From 058cf84c7b1e94b6cd3dc2c6b43b381da6f6b0c1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Oct 2019 17:27:55 +0200 Subject: [PATCH 001/183] cucumber-expressions: Use pseudo token rewrite parsing algorithm Splits the cucumber expression into text, optional, alternative and parameter tokens. This ensures that rewriting optionals does not interfere with rewriting alternatives. Currently processing and splitting is interleaved because the processing step may transform a token back into a text token again after handling its escapes. Fixes: #767 Closes: #770 --- .../CucumberExpression.java | 185 +++++++++++++----- .../CucumberExpressionPatternTest.java | 8 + .../CucumberExpressionTest.java | 5 + 3 files changed, 146 insertions(+), 52 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 9b7dbd43a3..b299db06fb 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -7,6 +7,9 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static java.util.Collections.singletonList; @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { @@ -15,7 +18,7 @@ public final class CucumberExpression implements Expression { @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces static final Pattern PARAMETER_PATTERN = Pattern.compile("(\\\\\\\\)?\\{([^}]*)\\}"); private static final Pattern OPTIONAL_PATTERN = Pattern.compile("(\\\\\\\\)?\\(([^)]+)\\)"); - private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s^/]+)((/[^\\s^/]+)+)"); + private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s^/)]+)((/[^\\s^/(]+)+)"); private static final String DOUBLE_ESCAPE = "\\\\"; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; @@ -25,44 +28,106 @@ public final class CucumberExpression implements Expression { private final TreeRegexp treeRegexp; private final ParameterTypeRegistry parameterTypeRegistry; + private static class Token { + private Token(String text, Type type) { + this.text = text; + this.type = type; + } + + private enum Type { + TEXT, OPTIONAL, ALTERNATION, PARAMETER + } + + String text; + Type type; + } + + CucumberExpression(String expression, ParameterTypeRegistry parameterTypeRegistry) { this.source = expression; this.parameterTypeRegistry = parameterTypeRegistry; expression = processEscapes(expression); - expression = processOptional(expression); - expression = processAlternation(expression); - expression = processParameters(expression, parameterTypeRegistry); - expression = "^" + expression + "$"; + List tokens = singletonList(new Token(expression, Token.Type.TEXT)); + + tokens = splitTextSections(tokens, OPTIONAL_PATTERN, Token.Type.OPTIONAL); + tokens = processOptional(tokens); + + tokens = splitTextSections(tokens, ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, Token.Type.ALTERNATION); + tokens = processAlternation(tokens); + + tokens = splitTextSections(tokens, PARAMETER_PATTERN, Token.Type.PARAMETER); + tokens = processParameters(tokens, parameterTypeRegistry); + + expression = "^" + join(tokens) + "$"; treeRegexp = new TreeRegexp(expression); } + private String join(List tokens) { + return tokens.stream() + .map(token -> token.text) + .collect(Collectors.joining()); + } + + private List splitTextSections(List unprocessed, Pattern pattern, Token.Type type) { + List tokens = new ArrayList<>(); + for (Token token : unprocessed) { + if (token.type != Token.Type.TEXT) { + tokens.add(token); + continue; + } + String expression = token.text; + Matcher matcher = pattern.matcher(expression); + int previousEnd = 0; + while (matcher.find()) { + int start = matcher.start(); + int end = matcher.end(); + String prefix = expression.substring(previousEnd, start); + tokens.add(new Token(prefix, Token.Type.TEXT)); + String match = expression.substring(start, end); + tokens.add(new Token(match, type)); + previousEnd = end; + } + String suffix = expression.substring(previousEnd); + tokens.add(new Token(suffix, Token.Type.TEXT)); + } + return tokens; + } + private String processEscapes(String expression) { // This will cause explicitly-escaped parentheses to be double-escaped return ESCAPE_PATTERN.matcher(expression).replaceAll("\\\\$1"); } - private String processAlternation(String expression) { - Matcher matcher = ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.matcher(expression); - StringBuffer sb = new StringBuffer(); - while (matcher.find()) { - // replace \/ with / - // replace / with | - String replacement = matcher.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); - - if (replacement.contains("|")) { - // Make sure the alternative parts don't contain parameter types - for (String part : replacement.split("\\|")) { - checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); + private List processAlternation(List ex) { + for (Token token : ex) { + if (token.type != Token.Type.ALTERNATION) { + continue; + } + String expression = token.text; + Matcher matcher = ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.matcher(expression); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + // replace \/ with / + // replace / with | + String replacement = matcher.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); + + if (replacement.contains("|")) { + // Make sure the alternative parts don't contain parameter types + for (String part : replacement.split("\\|")) { + checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); + } + matcher.appendReplacement(sb, "(?:" + replacement + ")"); + } else { + // All / were escaped + token.type = Token.Type.TEXT; + matcher.appendReplacement(sb, replacement); } - matcher.appendReplacement(sb, "(?:" + replacement + ")"); - } else { - // All / were escaped - matcher.appendReplacement(sb, replacement); } + matcher.appendTail(sb); + token.text = sb.toString(); } - matcher.appendTail(sb); - return sb.toString(); + return ex; } private void checkNotParameterType(String s, String message) { @@ -72,42 +137,58 @@ private void checkNotParameterType(String s, String message) { } } - private String processOptional(String expression) { - Matcher matcher = OPTIONAL_PATTERN.matcher(expression); - StringBuffer sb = new StringBuffer(); - while (matcher.find()) { - // look for double-escaped parentheses - String parameterPart = matcher.group(2); - if (DOUBLE_ESCAPE.equals(matcher.group(1))) { - matcher.appendReplacement(sb, "\\\\(" + parameterPart + "\\\\)"); - } else { - checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - matcher.appendReplacement(sb, "(?:" + parameterPart + ")?"); + private List processOptional(List tokens) { + for (Token token : tokens) { + if (token.type != Token.Type.OPTIONAL) { + continue; + } + String expression = token.text; + Matcher matcher = OPTIONAL_PATTERN.matcher(expression); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + // look for double-escaped parentheses + String parameterPart = matcher.group(2); + if (DOUBLE_ESCAPE.equals(matcher.group(1))) { + matcher.appendReplacement(sb, "\\\\(" + parameterPart + "\\\\)"); + token.type = Token.Type.TEXT; + } else { + checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); + matcher.appendReplacement(sb, "(?:" + parameterPart + ")?"); + } } + matcher.appendTail(sb); + token.text = sb.toString(); } - matcher.appendTail(sb); - return sb.toString(); + return tokens; } - private String processParameters(String expression, ParameterTypeRegistry parameterTypeRegistry) { - StringBuffer sb = new StringBuffer(); - Matcher matcher = PARAMETER_PATTERN.matcher(expression); - while (matcher.find()) { - if (DOUBLE_ESCAPE.equals(matcher.group(1))) { - matcher.appendReplacement(sb, "\\\\{" + matcher.group(2) + "\\\\}"); - } else { - String typeName = matcher.group(2); - ParameterType.checkParameterTypeName(typeName); - ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); - if (parameterType == null) { - throw new UndefinedParameterTypeException(typeName); + private List processParameters(List tokens, ParameterTypeRegistry parameterTypeRegistry) { + for (Token token : tokens) { + if (token.type != Token.Type.PARAMETER) { + continue; + } + String expression = token.text; + StringBuffer sb = new StringBuffer(); + Matcher matcher = PARAMETER_PATTERN.matcher(expression); + while (matcher.find()) { + if (DOUBLE_ESCAPE.equals(matcher.group(1))) { + matcher.appendReplacement(sb, "\\\\{" + matcher.group(2) + "\\\\}"); + token.type = Token.Type.TEXT; + } else { + String typeName = matcher.group(2); + ParameterType.checkParameterTypeName(typeName); + ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); + if (parameterType == null) { + throw new UndefinedParameterTypeException(typeName); + } + parameterTypes.add(parameterType); + matcher.appendReplacement(sb, Matcher.quoteReplacement(buildCaptureRegexp(parameterType.getRegexps()))); } - parameterTypes.add(parameterType); - matcher.appendReplacement(sb, Matcher.quoteReplacement(buildCaptureRegexp(parameterType.getRegexps()))); } + matcher.appendTail(sb); + token.text = sb.toString(); } - matcher.appendTail(sb); - return sb.toString(); + return tokens; } private String buildCaptureRegexp(List regexps) { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java index 0f16c39f54..333d91e863 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java @@ -36,6 +36,14 @@ public void translates_alternation_with_non_alpha() { ); } + @Test + public void translates_alternation_with_optional_words() { + assertPattern( + "the (test )chat/call/email interactions are visible", + "^the (?:test )?(?:chat|call|email) interactions are visible$" + ); + } + @Test public void translates_parameters() { assertPattern( diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 4c7a3e6593..e5ed9f0fc5 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -37,6 +37,11 @@ public void matches_word() { assertEquals(singletonList("blind"), match("three {word} mice", "three blind mice")); } + @Test + public void matches_word_s() { + assertEquals(emptyList(), match("three (brown )mice/rats", "three brown rats")); + } + @Test public void matches_double_quoted_string() { assertEquals(singletonList("blind"), match("three {string} mice", "three \"blind\" mice")); From 1f3f801bd714fb25935157a908f3f3d1816282e9 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Oct 2019 17:29:29 +0200 Subject: [PATCH 002/183] Revert pattern change --- .../io/cucumber/cucumberexpressions/CucumberExpression.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index b299db06fb..b0f35900c0 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -18,7 +18,7 @@ public final class CucumberExpression implements Expression { @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces static final Pattern PARAMETER_PATTERN = Pattern.compile("(\\\\\\\\)?\\{([^}]*)\\}"); private static final Pattern OPTIONAL_PATTERN = Pattern.compile("(\\\\\\\\)?\\(([^)]+)\\)"); - private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s^/)]+)((/[^\\s^/(]+)+)"); + private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s^/]+)((/[^\\s^/]+)+)"); private static final String DOUBLE_ESCAPE = "\\\\"; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; From 4ff1f46b6549b8a3b1e2c8797d45bc7e4674661e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Oct 2019 17:43:35 +0200 Subject: [PATCH 003/183] Edge cases --- .../CucumberExpressionTest.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index e5ed9f0fc5..5694ef8691 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -38,10 +38,24 @@ public void matches_word() { } @Test - public void matches_word_s() { + public void matches_optional_adjacent_to_alternative() { assertEquals(emptyList(), match("three (brown )mice/rats", "three brown rats")); } + @Test + public void matches_alternative_in_optional_as_text() { + //TODO: Does this make sense + assertEquals(emptyList(), match("three( brown/black) mice", "three brown/black mice")); + } + + @Test + public void matches_alternative_in_between_optional_as_text() { + //TODO: Does this make sense + assertEquals(emptyList(), match("three (brown)/(black) mice", "three brown/black mice")); + assertEquals(emptyList(), match("three (brown)/(black) mice", "three /black mice")); + assertEquals(emptyList(), match("three (brown)/(black) mice", "three brown/ mice")); + } + @Test public void matches_double_quoted_string() { assertEquals(singletonList("blind"), match("three {string} mice", "three \"blind\" mice")); From 44ebed254654285630498c39d4a3fd8c72ee368c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Oct 2019 17:54:48 +0200 Subject: [PATCH 004/183] Edge cases --- .../cucumberexpressions/CucumberExpression.java | 12 +++++++++++- .../cucumberexpressions/CucumberExpressionTest.java | 8 ++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index b0f35900c0..7770a14491 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -18,7 +18,7 @@ public final class CucumberExpression implements Expression { @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces static final Pattern PARAMETER_PATTERN = Pattern.compile("(\\\\\\\\)?\\{([^}]*)\\}"); private static final Pattern OPTIONAL_PATTERN = Pattern.compile("(\\\\\\\\)?\\(([^)]+)\\)"); - private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s^/]+)((/[^\\s^/]+)+)"); + private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s/]*)((/[^\\s/]*)+)"); private static final String DOUBLE_ESCAPE = "\\\\"; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; @@ -113,8 +113,12 @@ private List processAlternation(List ex) { String replacement = matcher.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); if (replacement.contains("|")) { + if(replacement.equals("|")){ + checkAlternativeNotEmpty("", expression); + } // Make sure the alternative parts don't contain parameter types for (String part : replacement.split("\\|")) { + checkAlternativeNotEmpty(part, expression); checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); } matcher.appendReplacement(sb, "(?:" + replacement + ")"); @@ -130,6 +134,12 @@ private List processAlternation(List ex) { return ex; } + private void checkAlternativeNotEmpty(String part, String source) { + if(part.isEmpty()){ + throw new CucumberExpressionException("Alternative may not be empty: " + source); + } + } + private void checkNotParameterType(String s, String message) { Matcher matcher = PARAMETER_PATTERN.matcher(s); if (matcher.find()) { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 5694ef8691..c9901545f9 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -1,5 +1,6 @@ package io.cucumber.cucumberexpressions; +import jdk.nashorn.internal.ir.annotations.Ignore; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -49,11 +50,14 @@ public void matches_alternative_in_optional_as_text() { } @Test + @Ignore public void matches_alternative_in_between_optional_as_text() { //TODO: Does this make sense + // TODO: Improve exception + assertEquals(emptyList(), match("three brown//black mice", "three brown/black mice")); assertEquals(emptyList(), match("three (brown)/(black) mice", "three brown/black mice")); - assertEquals(emptyList(), match("three (brown)/(black) mice", "three /black mice")); - assertEquals(emptyList(), match("three (brown)/(black) mice", "three brown/ mice")); +// assertEquals(emptyList(), match("three (brown)/(black) mice", "three /black mice")); +// assertEquals(emptyList(), match("three (brown)/(black) mice", "three brown/ mice")); } @Test From 52fde505b15068e5cf2c73473dd59ebc68d84e39 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Oct 2019 17:55:45 +0200 Subject: [PATCH 005/183] Edge cases --- .../cucumber/cucumberexpressions/CucumberExpressionTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index c9901545f9..279e2e3a80 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -1,6 +1,5 @@ package io.cucumber.cucumberexpressions; -import jdk.nashorn.internal.ir.annotations.Ignore; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -49,8 +48,7 @@ public void matches_alternative_in_optional_as_text() { assertEquals(emptyList(), match("three( brown/black) mice", "three brown/black mice")); } - @Test - @Ignore + // @Test public void matches_alternative_in_between_optional_as_text() { //TODO: Does this make sense // TODO: Improve exception From 11270f591cb6662ead3fd6faaf8ba74b118918f9 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Oct 2019 21:58:30 +0200 Subject: [PATCH 006/183] Merge split and processing. Escape regex as the last step. Fixes #601. --- .../CucumberExpression.java | 181 +++++++----------- .../CucumberExpressionTest.java | 8 + 2 files changed, 78 insertions(+), 111 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 7770a14491..c4cf8c1871 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -4,7 +4,10 @@ import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.function.BiFunction; +import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -13,13 +16,12 @@ @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { - // Does not include (){} characters because they have special meaning - private static final Pattern ESCAPE_PATTERN = Pattern.compile("([\\\\^\\[$.|?*+\\]])"); + private static final Pattern ESCAPE_PATTERN = Pattern.compile("([\\\\^\\[({$.|?*+})\\]])"); @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces - static final Pattern PARAMETER_PATTERN = Pattern.compile("(\\\\\\\\)?\\{([^}]*)\\}"); - private static final Pattern OPTIONAL_PATTERN = Pattern.compile("(\\\\\\\\)?\\(([^)]+)\\)"); + static final Pattern PARAMETER_PATTERN = Pattern.compile("(\\\\)?\\{([^}]*)\\}"); + private static final Pattern OPTIONAL_PATTERN = Pattern.compile("(\\\\)?\\(([^)]+)\\)"); private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s/]*)((/[^\\s/]*)+)"); - private static final String DOUBLE_ESCAPE = "\\\\"; + private static final String ESCAPE = "\\"; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; @@ -29,6 +31,9 @@ public final class CucumberExpression implements Expression { private final ParameterTypeRegistry parameterTypeRegistry; private static class Token { + final String text; + final Type type; + private Token(String text, Type type) { this.text = text; this.type = type; @@ -37,9 +42,6 @@ private Token(String text, Type type) { private enum Type { TEXT, OPTIONAL, ALTERNATION, PARAMETER } - - String text; - Type type; } @@ -47,29 +49,24 @@ private enum Type { this.source = expression; this.parameterTypeRegistry = parameterTypeRegistry; - expression = processEscapes(expression); List tokens = singletonList(new Token(expression, Token.Type.TEXT)); - tokens = splitTextSections(tokens, OPTIONAL_PATTERN, Token.Type.OPTIONAL); - tokens = processOptional(tokens); - - tokens = splitTextSections(tokens, ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, Token.Type.ALTERNATION); - tokens = processAlternation(tokens); + tokens = splitTextSections(tokens, OPTIONAL_PATTERN, this::processOptional); + tokens = splitTextSections(tokens, ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, this::processAlternation); + tokens = splitTextSections(tokens, PARAMETER_PATTERN, this::processParameters); - tokens = splitTextSections(tokens, PARAMETER_PATTERN, Token.Type.PARAMETER); - tokens = processParameters(tokens, parameterTypeRegistry); - - expression = "^" + join(tokens) + "$"; + expression = "^" + escapeTextSectionsAndJoin(tokens) + "$"; treeRegexp = new TreeRegexp(expression); } - private String join(List tokens) { + private String escapeTextSectionsAndJoin(List tokens) { return tokens.stream() - .map(token -> token.text) + .map(token -> token.type != Token.Type.TEXT ? token.text : processEscapes(token.text)) .collect(Collectors.joining()); } - private List splitTextSections(List unprocessed, Pattern pattern, Token.Type type) { + + private List splitTextSections(List unprocessed, Pattern pattern, BiFunction processor) { List tokens = new ArrayList<>(); for (Token token : unprocessed) { if (token.type != Token.Type.TEXT) { @@ -83,61 +80,52 @@ private List splitTextSections(List unprocessed, Pattern pattern, int start = matcher.start(); int end = matcher.end(); String prefix = expression.substring(previousEnd, start); - tokens.add(new Token(prefix, Token.Type.TEXT)); - String match = expression.substring(start, end); - tokens.add(new Token(match, type)); + if(!prefix.isEmpty()) { + tokens.add(new Token(prefix, Token.Type.TEXT)); + } + tokens.add(processor.apply(expression, matcher.toMatchResult())); previousEnd = end; } + String suffix = expression.substring(previousEnd); - tokens.add(new Token(suffix, Token.Type.TEXT)); + if (!suffix.isEmpty()) { + tokens.add(new Token(suffix, Token.Type.TEXT)); + } } return tokens; } - private String processEscapes(String expression) { + private static String processEscapes(String text) { // This will cause explicitly-escaped parentheses to be double-escaped - return ESCAPE_PATTERN.matcher(expression).replaceAll("\\\\$1"); + return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); } - private List processAlternation(List ex) { - for (Token token : ex) { - if (token.type != Token.Type.ALTERNATION) { - continue; - } - String expression = token.text; - Matcher matcher = ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.matcher(expression); - StringBuffer sb = new StringBuffer(); - while (matcher.find()) { - // replace \/ with / - // replace / with | - String replacement = matcher.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); - - if (replacement.contains("|")) { - if(replacement.equals("|")){ - checkAlternativeNotEmpty("", expression); - } - // Make sure the alternative parts don't contain parameter types - for (String part : replacement.split("\\|")) { - checkAlternativeNotEmpty(part, expression); - checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); - } - matcher.appendReplacement(sb, "(?:" + replacement + ")"); - } else { - // All / were escaped - token.type = Token.Type.TEXT; - matcher.appendReplacement(sb, replacement); - } - } - matcher.appendTail(sb); - token.text = sb.toString(); + private Token processAlternation(String expression, MatchResult matchResult) { + // replace \/ with / + // replace / with | + String replacement = matchResult.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); + if (!replacement.contains("|")) { + // All / were escaped + return new Token(replacement, Token.Type.TEXT); } - return ex; - } - private void checkAlternativeNotEmpty(String part, String source) { - if(part.isEmpty()){ - throw new CucumberExpressionException("Alternative may not be empty: " + source); + // Make sure the alternative parts aren't empty and don't contain parameter types + String[] split = replacement.split("\\|"); + if (split.length == 0) { + throw new CucumberExpressionException("Alternative may not be empty: " + expression); + } + for (String part : split) { + if (part.isEmpty()) { + throw new CucumberExpressionException("Alternative may not be empty: " + expression); + } + checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); } + String pattern = Arrays.stream(split) + .map(s -> s.replace("/", "|")) + .map(CucumberExpression::processEscapes) + .collect(Collectors.joining("|", "(?:", ")")); + + return new Token(pattern, Token.Type.ALTERNATION); } private void checkNotParameterType(String s, String message) { @@ -147,58 +135,29 @@ private void checkNotParameterType(String s, String message) { } } - private List processOptional(List tokens) { - for (Token token : tokens) { - if (token.type != Token.Type.OPTIONAL) { - continue; - } - String expression = token.text; - Matcher matcher = OPTIONAL_PATTERN.matcher(expression); - StringBuffer sb = new StringBuffer(); - while (matcher.find()) { - // look for double-escaped parentheses - String parameterPart = matcher.group(2); - if (DOUBLE_ESCAPE.equals(matcher.group(1))) { - matcher.appendReplacement(sb, "\\\\(" + parameterPart + "\\\\)"); - token.type = Token.Type.TEXT; - } else { - checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - matcher.appendReplacement(sb, "(?:" + parameterPart + ")?"); - } - } - matcher.appendTail(sb); - token.text = sb.toString(); + private Token processOptional(String expression, MatchResult matchResult) { + // look for double-escaped parentheses + String parameterPart = matchResult.group(2); + if (ESCAPE.equals(matchResult.group(1))) { + return new Token("(" + parameterPart + ")", Token.Type.TEXT); } - return tokens; + + checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); + return new Token("(?:" + processEscapes(parameterPart) + ")?", Token.Type.OPTIONAL); } - private List processParameters(List tokens, ParameterTypeRegistry parameterTypeRegistry) { - for (Token token : tokens) { - if (token.type != Token.Type.PARAMETER) { - continue; - } - String expression = token.text; - StringBuffer sb = new StringBuffer(); - Matcher matcher = PARAMETER_PATTERN.matcher(expression); - while (matcher.find()) { - if (DOUBLE_ESCAPE.equals(matcher.group(1))) { - matcher.appendReplacement(sb, "\\\\{" + matcher.group(2) + "\\\\}"); - token.type = Token.Type.TEXT; - } else { - String typeName = matcher.group(2); - ParameterType.checkParameterTypeName(typeName); - ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); - if (parameterType == null) { - throw new UndefinedParameterTypeException(typeName); - } - parameterTypes.add(parameterType); - matcher.appendReplacement(sb, Matcher.quoteReplacement(buildCaptureRegexp(parameterType.getRegexps()))); - } - } - matcher.appendTail(sb); - token.text = sb.toString(); + private Token processParameters(String expression, MatchResult matchResult) { + String typeName = matchResult.group(2); + if (ESCAPE.equals(matchResult.group(1))) { + return new Token("{" + typeName + "}", Token.Type.TEXT); } - return tokens; + ParameterType.checkParameterTypeName(typeName); + ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); + if (parameterType == null) { + throw new UndefinedParameterTypeException(typeName); + } + parameterTypes.add(parameterType); + return new Token(buildCaptureRegexp(parameterType.getRegexps()), Token.Type.PARAMETER); } private String buildCaptureRegexp(List regexps) { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 279e2e3a80..02bb3ab3a9 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -42,6 +42,14 @@ public void matches_optional_adjacent_to_alternative() { assertEquals(emptyList(), match("three (brown )mice/rats", "three brown rats")); } + @Test + public void matches_alternative_after_optional() { + assertEquals(singletonList(2), match("I wait {int} second(s)./?", "I wait 2 second?")); + assertEquals(singletonList(2), match("I wait {int} second(s)./?", "I wait 2 seconds.")); + assertEquals(singletonList(1), match("I wait {int} second(s)./?", "I wait 1 second?")); + assertEquals(singletonList(1), match("I wait {int} second(s)./?", "I wait 1 second.")); + } + @Test public void matches_alternative_in_optional_as_text() { //TODO: Does this make sense From a66e5b1c57636b683f7ce71471994ca39066c1fa Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Oct 2019 22:27:09 +0200 Subject: [PATCH 007/183] Use some functional stuff --- .../CucumberExpression.java | 188 +++++++++--------- 1 file changed, 90 insertions(+), 98 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index c4cf8c1871..9d11a28deb 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -7,12 +7,13 @@ import java.util.Arrays; import java.util.List; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; +import java.util.stream.Stream; -import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.joining; @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { @@ -44,55 +45,22 @@ private enum Type { } } - CucumberExpression(String expression, ParameterTypeRegistry parameterTypeRegistry) { this.source = expression; this.parameterTypeRegistry = parameterTypeRegistry; - List tokens = singletonList(new Token(expression, Token.Type.TEXT)); - - tokens = splitTextSections(tokens, OPTIONAL_PATTERN, this::processOptional); - tokens = splitTextSections(tokens, ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, this::processAlternation); - tokens = splitTextSections(tokens, PARAMETER_PATTERN, this::processParameters); + String pattern = Stream.of(new Token(expression, Token.Type.TEXT)) + .flatMap(processOptional()) + .flatMap(processAlternation()) + .flatMap(processParameters()) + .map(escapeTextTokens()) + .collect(joining("", "^", "$")); - expression = "^" + escapeTextSectionsAndJoin(tokens) + "$"; - treeRegexp = new TreeRegexp(expression); + treeRegexp = new TreeRegexp(pattern); } - private String escapeTextSectionsAndJoin(List tokens) { - return tokens.stream() - .map(token -> token.type != Token.Type.TEXT ? token.text : processEscapes(token.text)) - .collect(Collectors.joining()); - } - - - private List splitTextSections(List unprocessed, Pattern pattern, BiFunction processor) { - List tokens = new ArrayList<>(); - for (Token token : unprocessed) { - if (token.type != Token.Type.TEXT) { - tokens.add(token); - continue; - } - String expression = token.text; - Matcher matcher = pattern.matcher(expression); - int previousEnd = 0; - while (matcher.find()) { - int start = matcher.start(); - int end = matcher.end(); - String prefix = expression.substring(previousEnd, start); - if(!prefix.isEmpty()) { - tokens.add(new Token(prefix, Token.Type.TEXT)); - } - tokens.add(processor.apply(expression, matcher.toMatchResult())); - previousEnd = end; - } - - String suffix = expression.substring(previousEnd); - if (!suffix.isEmpty()) { - tokens.add(new Token(suffix, Token.Type.TEXT)); - } - } - return tokens; + private static Function escapeTextTokens() { + return token -> token.type != Token.Type.TEXT ? token.text : processEscapes(token.text); } private static String processEscapes(String text) { @@ -100,32 +68,34 @@ private static String processEscapes(String text) { return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); } - private Token processAlternation(String expression, MatchResult matchResult) { - // replace \/ with / - // replace / with | - String replacement = matchResult.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); - if (!replacement.contains("|")) { - // All / were escaped - return new Token(replacement, Token.Type.TEXT); - } + private Function> processAlternation() { + return splitTextSections(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, (expression, matchResult) -> { + // replace \/ with / + // replace / with | + String replacement = matchResult.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); + if (!replacement.contains("|")) { + // All / were escaped + return new Token(replacement, Token.Type.TEXT); + } - // Make sure the alternative parts aren't empty and don't contain parameter types - String[] split = replacement.split("\\|"); - if (split.length == 0) { - throw new CucumberExpressionException("Alternative may not be empty: " + expression); - } - for (String part : split) { - if (part.isEmpty()) { + // Make sure the alternative parts aren't empty and don't contain parameter types + String[] split = replacement.split("\\|"); + if (split.length == 0) { throw new CucumberExpressionException("Alternative may not be empty: " + expression); } - checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); - } - String pattern = Arrays.stream(split) - .map(s -> s.replace("/", "|")) - .map(CucumberExpression::processEscapes) - .collect(Collectors.joining("|", "(?:", ")")); + for (String part : split) { + if (part.isEmpty()) { + throw new CucumberExpressionException("Alternative may not be empty: " + expression); + } + checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); + } + String pattern = Arrays.stream(split) + .map(s -> s.replace("/", "|")) + .map(CucumberExpression::processEscapes) + .collect(joining("|", "(?:", ")")); - return new Token(pattern, Token.Type.ALTERNATION); + return new Token(pattern, Token.Type.ALTERNATION); + }); } private void checkNotParameterType(String s, String message) { @@ -135,47 +105,69 @@ private void checkNotParameterType(String s, String message) { } } - private Token processOptional(String expression, MatchResult matchResult) { - // look for double-escaped parentheses - String parameterPart = matchResult.group(2); - if (ESCAPE.equals(matchResult.group(1))) { - return new Token("(" + parameterPart + ")", Token.Type.TEXT); - } + private Function> processOptional() { + return splitTextSections(OPTIONAL_PATTERN, (expression, matchResult) -> { + // look for double-escaped parentheses + String parameterPart = matchResult.group(2); + if (ESCAPE.equals(matchResult.group(1))) { + return new Token("(" + parameterPart + ")", Token.Type.TEXT); + } - checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - return new Token("(?:" + processEscapes(parameterPart) + ")?", Token.Type.OPTIONAL); + checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); + return new Token("(?:" + processEscapes(parameterPart) + ")?", Token.Type.OPTIONAL); + }); } - private Token processParameters(String expression, MatchResult matchResult) { - String typeName = matchResult.group(2); - if (ESCAPE.equals(matchResult.group(1))) { - return new Token("{" + typeName + "}", Token.Type.TEXT); - } - ParameterType.checkParameterTypeName(typeName); - ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); - if (parameterType == null) { - throw new UndefinedParameterTypeException(typeName); - } - parameterTypes.add(parameterType); - return new Token(buildCaptureRegexp(parameterType.getRegexps()), Token.Type.PARAMETER); + private Function> processParameters() { + return splitTextSections(PARAMETER_PATTERN, (expression, matchResult) -> { + String typeName = matchResult.group(2); + if (ESCAPE.equals(matchResult.group(1))) { + return new Token("{" + typeName + "}", Token.Type.TEXT); + } + ParameterType.checkParameterTypeName(typeName); + ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); + if (parameterType == null) { + throw new UndefinedParameterTypeException(typeName); + } + parameterTypes.add(parameterType); + return new Token(buildCaptureRegexp(parameterType.getRegexps()), Token.Type.PARAMETER); + }); } private String buildCaptureRegexp(List regexps) { - StringBuilder sb = new StringBuilder("("); - if (regexps.size() == 1) { - sb.append(regexps.get(0)); - } else { - boolean bar = false; - for (String captureGroupRegexp : regexps) { - if (bar) sb.append("|"); - sb.append("(?:").append(captureGroupRegexp).append(")"); - bar = true; - } + return "(" + regexps.get(0) + ")"; } + return regexps.stream() + .collect(joining(")|(?:", "((?:","))")); + } + + private static Function> splitTextSections(Pattern pattern, BiFunction processor) { + return token -> { + if (token.type != Token.Type.TEXT) { + return Stream.of(token); + } + String expression = token.text; + List tokens = new ArrayList<>(); + Matcher matcher = pattern.matcher(token.text); + int previousEnd = 0; + while (matcher.find()) { + int start = matcher.start(); + int end = matcher.end(); + String prefix = expression.substring(previousEnd, start); + if (!prefix.isEmpty()) { + tokens.add(new Token(prefix, Token.Type.TEXT)); + } + tokens.add(processor.apply(expression, matcher.toMatchResult())); + previousEnd = end; + } - sb.append(")"); - return sb.toString(); + String suffix = expression.substring(previousEnd); + if (!suffix.isEmpty()) { + tokens.add(new Token(suffix, Token.Type.TEXT)); + } + return tokens.stream(); + }; } @Override From b6d6d30f6559804eaff0e9adb848409a71268242 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Oct 2019 22:43:29 +0200 Subject: [PATCH 008/183] Add grammar rules --- cucumber-expressions/java/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cucumber-expressions/java/README.md b/cucumber-expressions/java/README.md index 1515320468..c654eceff6 100644 --- a/cucumber-expressions/java/README.md +++ b/cucumber-expressions/java/README.md @@ -3,3 +3,20 @@ # Cucumber Expressions for Java [The docs are here](https://cucumber.io/docs/cucumber/cucumber-expressions/). + + +## Grammar ## + +``` +cucumber-expression := [ optional | escaped-optional | other-then-optional ]* +optional := '(' + text + ')' +escaped-optional := '\(' + other-then-optional + ')' +other-then-optional: = [ alternative | escaped-alternative | other-then-alternative ]* +alternative := text + [ '/' + text ]+ +escaped-alternative := other-then-alternative +[ '\/' + other-then-alternative ]+ +other-then-alternative := [ parameter | escaped-parameter | other-then-parameter ]* +parameter := '{' + text + '}' +escaped-parameter := '\{' + other-then-parameter + '}' +other-then-parameter:= text +text := .* +``` \ No newline at end of file From a450a460938113a6aaf66d9e54e3e56bf365afe5 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 25 Oct 2019 14:26:48 +0200 Subject: [PATCH 009/183] Fix tests --- .../CucumberExpression.java | 13 +++++----- .../CucumberExpressionTest.java | 26 +++++++++++-------- .../ExpressionExamplesTest.java | 4 +-- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 9d11a28deb..6b62af4f60 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -25,6 +25,7 @@ public final class CucumberExpression implements Expression { private static final String ESCAPE = "\\"; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; + private static final String ALTERNATIVE_MAY_NOT_BE_EMPTY = "Alternative may not be empty: "; private final List> parameterTypes = new ArrayList<>(); private final String source; @@ -69,7 +70,7 @@ private static String processEscapes(String text) { } private Function> processAlternation() { - return splitTextSections(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, (expression, matchResult) -> { + return splitTextTokens(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, (expression, matchResult) -> { // replace \/ with / // replace / with | String replacement = matchResult.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); @@ -81,11 +82,11 @@ private Function> processAlternation() { // Make sure the alternative parts aren't empty and don't contain parameter types String[] split = replacement.split("\\|"); if (split.length == 0) { - throw new CucumberExpressionException("Alternative may not be empty: " + expression); + throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + expression); } for (String part : split) { if (part.isEmpty()) { - throw new CucumberExpressionException("Alternative may not be empty: " + expression); + throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + expression); } checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); } @@ -106,7 +107,7 @@ private void checkNotParameterType(String s, String message) { } private Function> processOptional() { - return splitTextSections(OPTIONAL_PATTERN, (expression, matchResult) -> { + return splitTextTokens(OPTIONAL_PATTERN, (expression, matchResult) -> { // look for double-escaped parentheses String parameterPart = matchResult.group(2); if (ESCAPE.equals(matchResult.group(1))) { @@ -119,7 +120,7 @@ private Function> processOptional() { } private Function> processParameters() { - return splitTextSections(PARAMETER_PATTERN, (expression, matchResult) -> { + return splitTextTokens(PARAMETER_PATTERN, (expression, matchResult) -> { String typeName = matchResult.group(2); if (ESCAPE.equals(matchResult.group(1))) { return new Token("{" + typeName + "}", Token.Type.TEXT); @@ -142,7 +143,7 @@ private String buildCaptureRegexp(List regexps) { .collect(joining(")|(?:", "((?:","))")); } - private static Function> splitTextSections(Pattern pattern, BiFunction processor) { + private static Function> splitTextTokens(Pattern pattern, BiFunction processor) { return token -> { if (token.type != Token.Type.TEXT) { return Stream.of(token); diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 02bb3ab3a9..46a42f68fc 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -52,20 +52,9 @@ public void matches_alternative_after_optional() { @Test public void matches_alternative_in_optional_as_text() { - //TODO: Does this make sense assertEquals(emptyList(), match("three( brown/black) mice", "three brown/black mice")); } - // @Test - public void matches_alternative_in_between_optional_as_text() { - //TODO: Does this make sense - // TODO: Improve exception - assertEquals(emptyList(), match("three brown//black mice", "three brown/black mice")); - assertEquals(emptyList(), match("three (brown)/(black) mice", "three brown/black mice")); -// assertEquals(emptyList(), match("three (brown)/(black) mice", "three /black mice")); -// assertEquals(emptyList(), match("three (brown)/(black) mice", "three brown/ mice")); - } - @Test public void matches_double_quoted_string() { assertEquals(singletonList("blind"), match("three {string} mice", "three \"blind\" mice")); @@ -217,6 +206,21 @@ public void does_not_allow_parameter_type_text_alternation() { assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Parameter types cannot be alternative: {int}/x"))); } + + @Test + public void does_not_allow_empty_alternative() { + Executable testMethod = () -> match("three brown//black mice", "three brown mice"); + CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); + assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: three brown//black mice"))); + } + + @Test + public void does_not_allow_optional_adjacent_to_alternative() { + Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); + CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); + assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: /black mice"))); + } + @Test public void exposes_source() { String expr = "I have {int} cuke(s)"; diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionExamplesTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionExamplesTest.java index 086f2f7126..2b1c08eb50 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionExamplesTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionExamplesTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -27,7 +27,7 @@ static Collection data() throws IOException { final Collection data = new ArrayList<>(); - String s = new String(readAllBytes(get("examples.txt")), Charset.forName("UTF-8")); + String s = new String(readAllBytes(get("examples.txt")), StandardCharsets.UTF_8); String[] chunks = s.split("---"); for (String chunk : chunks) { chunk = chunk.trim(); From 4d788fcee3ad0606d7f6801f743e2be7d4bd17a2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 25 Oct 2019 16:02:27 +0200 Subject: [PATCH 010/183] WIP Golang impl --- .../go/cucumber_expression.go | 117 ++++++++++++------ 1 file changed, 80 insertions(+), 37 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index f3c09c19a0..da0f6ea2f3 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -7,11 +7,30 @@ import ( "strings" ) -var ESCAPE_REGEXP = regexp.MustCompile(`([\\^[$.|?*+])`) -var PARAMETER_REGEXP = regexp.MustCompile(`(\\\\\\\\)?{([^}]*)}`) -var OPTIONAL_REGEXP = regexp.MustCompile(`(\\\\\\\\)?\([^)]+\)`) -var ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = regexp.MustCompile(`([^\s^/]+)((/[^\s^/]+)+)`) -var DOUBLE_ESCAPE = `\\\\` +const alternativesMayNotBeEmpty = "Alternatives may not be empty: %s" +const parameterTypesCanNotBeAlternative = "Parameter types cannot be alternative: %s" + +var escapeRegexp = regexp.MustCompile(`([\\^[({$.|?*+})\]])`) +var parameterRegexp = regexp.MustCompile(`(\\\\\\\\)?{([^}]*)}`) +var optionalRegexp = regexp.MustCompile(`(\\\\\\\\)?\([^)]+\)`) +var alternativeNonWhitespaceTextRegexp = regexp.MustCompile(`([^\s^/]+)((/[^\s^/]+)+)`) + +const doubleEscape = `\\` +const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" + +type tokenType int + +const ( + text tokenType = 0 + optional tokenType = 1 + alternation tokenType = 2 + parameter tokenType = 3 +) + +type token struct { + text string + tokenType tokenType +} type CucumberExpression struct { source string @@ -23,19 +42,20 @@ type CucumberExpression struct { func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (Expression, error) { result := &CucumberExpression{source: expression, parameterTypeRegistry: parameterTypeRegistry} - expression = result.processEscapes(expression) + tokens := make([]token, 1) + tokens = append(tokens, token{expression, text}) - expression, err := result.processOptional(expression) + tokens, err := result.processOptional(tokens) if err != nil { return nil, err } - expression, err = result.processAlteration(expression) + tokens, err = result.processAlteration(tokens) if err != nil { return nil, err } - expression, err = result.processParameters(expression, parameterTypeRegistry) + tokens, err = result.processParameters(tokens, parameterTypeRegistry) if err != nil { return nil, err } @@ -79,71 +99,94 @@ func (c *CucumberExpression) Source() string { } func (c *CucumberExpression) processEscapes(expression string) string { - return ESCAPE_REGEXP.ReplaceAllString(expression, `\$1`) + return escapeRegexp.ReplaceAllString(expression, `\$1`) } -func (c *CucumberExpression) processOptional(expression string) (string, error) { +func (c *CucumberExpression) processOptional(expression []token) ([]token, error) { var err error - result := OPTIONAL_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { - if strings.HasPrefix(match, DOUBLE_ESCAPE) { - return fmt.Sprintf(`\(%s\)`, match[5:len(match)-1]) + result := splitTextTokens(expression, optionalRegexp, func(match string) (token) { + // look for double-escaped parentheses + if strings.HasPrefix(match, doubleEscape) { + return token{fmt.Sprintf(`(%s)`, match[5:len(match)-1]), text} } - if PARAMETER_REGEXP.MatchString(match) { - err = NewCucumberExpressionError(fmt.Sprintf("Parameter types cannot be optional: %s", c.source)) - return match + if parameterRegexp.MatchString(match) { + err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeOptional, c.source)) } - return fmt.Sprintf("(?:%s)?", match[1:len(match)-1]) + return token{fmt.Sprintf("(?:%s)?", match[1:len(match)-1]), optional} }) return result, err } -func (c *CucumberExpression) processAlteration(expression string) (string, error) { +func (c *CucumberExpression) processAlteration(expression []token) ([]token, error) { var err error - result := ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { + result := splitTextTokens(expression, alternativeNonWhitespaceTextRegexp, func(match string) token { // replace \/ with / // replace / with | replacement := strings.Replace(match, "/", "|", -1) replacement = strings.Replace(replacement, `\\\\|`, "/", -1) - if strings.Contains(replacement, "|") { - parts := strings.Split(replacement, ":") - for _, part := range parts { - if PARAMETER_REGEXP.MatchString(part) { - err = NewCucumberExpressionError(fmt.Sprintf("Parameter types cannot be alternative: %s", c.source)) - return match - } + if !strings.Contains(replacement, "|") { + // All / were escaped + return token{replacement, text} + } + + // Make sure the alternative parts aren't empty and don't contain parameter types + parts := strings.Split(replacement, "|") + + if len(parts) == 0 { + err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) + } + for _, part := range parts { + if len(part) == 0 { + err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) + } + if parameterRegexp.MatchString(part) { + err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeAlternative, c.source)) } - return fmt.Sprintf("(?:%s)", replacement) } + return token{fmt.Sprintf("(?:%s)", replacement), alternation} - return replacement }) return result, err } -func (c *CucumberExpression) processParameters(expression string, parameterTypeRegistry *ParameterTypeRegistry) (string, error) { +func (c *CucumberExpression) processParameters(expression []token, parameterTypeRegistry *ParameterTypeRegistry) ([]token, error) { var err error - result := PARAMETER_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { - if strings.HasPrefix(match, DOUBLE_ESCAPE) { - return fmt.Sprintf(`\{%s\}`, match[5:len(match)-1]) + result := splitTextTokens(expression, alternativeNonWhitespaceTextRegexp, func(match string) token { + if strings.HasPrefix(match, doubleEscape) { + return token{fmt.Sprintf(`{%s}`, match[5:len(match)-1]), text} } - typeName := match[1 : len(match)-1] err = CheckParameterTypeName(typeName) if err != nil { - return "" + return token{match, parameter} } parameterType := parameterTypeRegistry.LookupByTypeName(typeName) if parameterType == nil { err = NewUndefinedParameterTypeError(typeName) - return match + return token{match, parameter} } c.parameterTypes = append(c.parameterTypes, parameterType) - return buildCaptureRegexp(parameterType.regexps) + return token{buildCaptureRegexp(parameterType.regexps), parameter} }) return result, err } +func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func(string) (token)) ([]token) { + newTokens := make([]token, len(tokens)) + for _, token := range tokens { + if token.tokenType != text { + newTokens = append(newTokens, token) + continue + } + regexp.FindAllStringSubmatchIndex() + //TODO: You wer here. + } + + + return newTokens +} + func buildCaptureRegexp(regexps []*regexp.Regexp) string { if len(regexps) == 1 { return fmt.Sprintf("(%s)", regexps[0].String()) From dbe4b9c015f21f1793e813b6a7be26e18bf53ff1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 26 Oct 2019 17:00:44 +0200 Subject: [PATCH 011/183] Fix alternation --- .../cucumberexpressions/CucumberExpression.java | 13 ++++++------- .../cucumberexpressions/CucumberExpressionTest.java | 5 +++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 6b62af4f60..83bc81a9cb 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -80,18 +80,17 @@ private Function> processAlternation() { } // Make sure the alternative parts aren't empty and don't contain parameter types - String[] split = replacement.split("\\|"); - if (split.length == 0) { + String[] alternatives = replacement.split("\\|"); + if (alternatives.length == 0) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + expression); } - for (String part : split) { - if (part.isEmpty()) { + for (String alternative : alternatives) { + if (alternative.isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + expression); } - checkNotParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); + checkNotParameterType(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); } - String pattern = Arrays.stream(split) - .map(s -> s.replace("/", "|")) + String pattern = Arrays.stream(alternatives) .map(CucumberExpression::processEscapes) .collect(joining("|", "(?:", ")")); diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 46a42f68fc..312eeaa667 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -42,6 +42,11 @@ public void matches_optional_adjacent_to_alternative() { assertEquals(emptyList(), match("three (brown )mice/rats", "three brown rats")); } + @Test + public void matches_alternation() { + assertEquals(emptyList(), match("mice/rats and rats\\/mice", "rats and rats/mice")); + } + @Test public void matches_alternative_after_optional() { assertEquals(singletonList(2), match("I wait {int} second(s)./?", "I wait 2 second?")); From e8ce052a2d48d7784b99d8236c03f684a9716f13 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 26 Oct 2019 17:16:49 +0200 Subject: [PATCH 012/183] Tests pass --- .../go/cucumber_expression.go | 94 ++++++++++++------- .../go/cucumber_expression_regexp_test.go | 12 ++- .../go/cucumber_expression_test.go | 6 +- .../CucumberExpressionTest.java | 45 +++++---- 4 files changed, 97 insertions(+), 60 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index da0f6ea2f3..387afa12c2 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -9,14 +9,14 @@ import ( const alternativesMayNotBeEmpty = "Alternatives may not be empty: %s" const parameterTypesCanNotBeAlternative = "Parameter types cannot be alternative: %s" +const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" -var escapeRegexp = regexp.MustCompile(`([\\^[({$.|?*+})\]])`) -var parameterRegexp = regexp.MustCompile(`(\\\\\\\\)?{([^}]*)}`) -var optionalRegexp = regexp.MustCompile(`(\\\\\\\\)?\([^)]+\)`) -var alternativeNonWhitespaceTextRegexp = regexp.MustCompile(`([^\s^/]+)((/[^\s^/]+)+)`) +var escapeRegexp = regexp.MustCompile(`([\\^\[({$.|?*+})\]])`) +var parameterRegexp = regexp.MustCompile(`(\\)?{([^}]*)}`) +var optionalRegexp = regexp.MustCompile(`(\\)?\([^)]+\)`) +var alternativeNonWhitespaceTextRegexp = regexp.MustCompile(`([^\s/]*)((/[^\s/]*)+)`) -const doubleEscape = `\\` -const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" +const doubleEscape = `\` type tokenType int @@ -42,7 +42,7 @@ type CucumberExpression struct { func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (Expression, error) { result := &CucumberExpression{source: expression, parameterTypeRegistry: parameterTypeRegistry} - tokens := make([]token, 1) + tokens := make([]token, 0) tokens = append(tokens, token{expression, text}) tokens, err := result.processOptional(tokens) @@ -60,9 +60,8 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return nil, err } - expression = "^" + expression + "$" - - result.treeRegexp = NewTreeRegexp(regexp.MustCompile(expression)) + pattern := result.escapeTextTokensAndJoin(tokens, "^", "$") + result.treeRegexp = NewTreeRegexp(regexp.MustCompile(pattern)) return result, nil } @@ -98,7 +97,22 @@ func (c *CucumberExpression) Source() string { return c.source } +func (c *CucumberExpression) escapeTextTokensAndJoin(expression []token, prefix string, suffix string) string { + var builder strings.Builder + builder.WriteString(prefix) + for _, token := range expression { + if token.tokenType == text { + builder.WriteString(c.processEscapes(token.text)) + } else { + builder.WriteString(token.text) + } + } + builder.WriteString(suffix) + return builder.String() +} + func (c *CucumberExpression) processEscapes(expression string) string { + // This will cause explicitly-escaped parentheses to be double-escaped return escapeRegexp.ReplaceAllString(expression, `\$1`) } @@ -107,12 +121,13 @@ func (c *CucumberExpression) processOptional(expression []token) ([]token, error result := splitTextTokens(expression, optionalRegexp, func(match string) (token) { // look for double-escaped parentheses if strings.HasPrefix(match, doubleEscape) { - return token{fmt.Sprintf(`(%s)`, match[5:len(match)-1]), text} + return token{fmt.Sprintf(`(%s)`, match[2:len(match)-1]), text} } if parameterRegexp.MatchString(match) { err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeOptional, c.source)) } - return token{fmt.Sprintf("(?:%s)?", match[1:len(match)-1]), optional} + optionalText := c.processEscapes(match[1 : len(match)-1]) + return token{fmt.Sprintf("(?:%s)?", optionalText), optional} }) return result, err } @@ -123,36 +138,37 @@ func (c *CucumberExpression) processAlteration(expression []token) ([]token, err // replace \/ with / // replace / with | replacement := strings.Replace(match, "/", "|", -1) - replacement = strings.Replace(replacement, `\\\\|`, "/", -1) + replacement = strings.Replace(replacement, `\|`, "/", -1) if !strings.Contains(replacement, "|") { // All / were escaped return token{replacement, text} } - // Make sure the alternative parts aren't empty and don't contain parameter types - parts := strings.Split(replacement, "|") - - if len(parts) == 0 { + // Make sure the alternative alternatives aren't empty and don't contain parameter types + alternatives := strings.Split(replacement, "|") + alternativeTexts := make([]string,0) + if len(alternatives) == 0 { err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) } - for _, part := range parts { - if len(part) == 0 { + for _, alternative := range alternatives { + if len(alternative) == 0 { err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) } - if parameterRegexp.MatchString(part) { + if parameterRegexp.MatchString(alternative) { err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeAlternative, c.source)) } + alternativeTexts = append(alternativeTexts, c.processEscapes(alternative)) } - return token{fmt.Sprintf("(?:%s)", replacement), alternation} - + alternativeText := strings.Join(alternativeTexts, "|") + return token{fmt.Sprintf("(?:%s)", alternativeText), alternation} }) return result, err } func (c *CucumberExpression) processParameters(expression []token, parameterTypeRegistry *ParameterTypeRegistry) ([]token, error) { var err error - result := splitTextTokens(expression, alternativeNonWhitespaceTextRegexp, func(match string) token { + result := splitTextTokens(expression, parameterRegexp, func(match string) token { if strings.HasPrefix(match, doubleEscape) { return token{fmt.Sprintf(`{%s}`, match[5:len(match)-1]), text} } @@ -172,18 +188,32 @@ func (c *CucumberExpression) processParameters(expression []token, parameterType return result, err } -func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func(string) (token)) ([]token) { - newTokens := make([]token, len(tokens)) - for _, token := range tokens { - if token.tokenType != text { - newTokens = append(newTokens, token) +func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func(string) token) []token { + newTokens := make([]token, 0) + for _, t := range tokens { + if t.tokenType != text { + newTokens = append(newTokens, t) continue } - regexp.FindAllStringSubmatchIndex() - //TODO: You wer here. + expression := t.text + loc := regexp.FindAllStringIndex(expression, -1) + previousEnd := 0 + for i := 0; i < len(loc); i++ { + start := loc[i][0] + end := loc[i][1] + prefix := expression[previousEnd:start] + if len(prefix) > 0 { + newTokens = append(newTokens, token{prefix, text}) + } + match := expression[start:end] + newTokens = append(newTokens, processor(match)) + previousEnd = end + } + suffix := expression[previousEnd:] + if len(suffix) > 0 { + newTokens = append(newTokens, token{suffix, text}) + } } - - return newTokens } diff --git a/cucumber-expressions/go/cucumber_expression_regexp_test.go b/cucumber-expressions/go/cucumber_expression_regexp_test.go index 5f8c172d7f..48ba3efd71 100644 --- a/cucumber-expressions/go/cucumber_expression_regexp_test.go +++ b/cucumber-expressions/go/cucumber_expression_regexp_test.go @@ -2,7 +2,6 @@ package cucumberexpressions import ( "testing" - "github.com/stretchr/testify/require" ) @@ -30,6 +29,13 @@ func TestCucumberExpressionRegExpTranslation(t *testing.T) { "^I said (?:Alpha1|Beta1)$", ) }) + t.Run("translates alternation with optional words", func(t *testing.T) { + assertRegexp( + t, + "the (test )chat/call/email interactions are visible", + "^the (?:test )?(?:chat|call|email) interactions are visible$", + ) + }) t.Run("translates parameters", func(t *testing.T) { assertRegexp( @@ -54,11 +60,13 @@ func TestCucumberExpressionRegExpTranslation(t *testing.T) { `^Привет, Мир(?:ы)?!$`, ) }) + + } func assertRegexp(t *testing.T, expression string, expectedRegexp string) { parameterTypeRegistry := NewParameterTypeRegistry() generator, err := NewCucumberExpression(expression, parameterTypeRegistry) require.NoError(t, err) - require.Equal(t, generator.Regexp().String(), expectedRegexp) + require.Equal(t, expectedRegexp, generator.Regexp().String()) } diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 250eb76051..31845f6e24 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -136,7 +136,7 @@ func TestCucumberExpression(t *testing.T) { t.Run("matches escaped parenthesis", func(t *testing.T) { require.Equal( t, - MatchCucumberExpression(t, "three \\\\(exceptionally) {string} mice", `three (exceptionally) "blind" mice`), + MatchCucumberExpression(t, "three \\(exceptionally) {string} mice", `three (exceptionally) "blind" mice`), []interface{}{"blind"}, ) }) @@ -144,7 +144,7 @@ func TestCucumberExpression(t *testing.T) { t.Run("matches escaped slash", func(t *testing.T) { require.Equal( t, - MatchCucumberExpression(t, "12\\\\/2020", `12/2020`), + MatchCucumberExpression(t, "12\\/2020", `12/2020`), []interface{}{}, ) }) @@ -242,7 +242,7 @@ func TestCucumberExpression(t *testing.T) { t.Run("allows escaped optional parameters", func(t *testing.T) { require.Equal( t, - MatchCucumberExpression(t, "\\\\({int})", `(3)`), + MatchCucumberExpression(t, "\\({int})", `(3)`), []interface{}{3}, ) }) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 312eeaa667..87ffbdd99c 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -38,27 +38,40 @@ public void matches_word() { } @Test - public void matches_optional_adjacent_to_alternative() { - assertEquals(emptyList(), match("three (brown )mice/rats", "three brown rats")); + public void matches_alternation() { + assertEquals(emptyList(), match("mice/rats and rats\\/mice", "rats and rats/mice")); } @Test - public void matches_alternation() { - assertEquals(emptyList(), match("mice/rats and rats\\/mice", "rats and rats/mice")); + public void matches_optional_before_alternation() { + assertEquals(emptyList(), match("three (brown )mice/rats", "three brown rats")); } @Test - public void matches_alternative_after_optional() { + public void matches_optional_before_alternation_with_regex_characters() { assertEquals(singletonList(2), match("I wait {int} second(s)./?", "I wait 2 second?")); - assertEquals(singletonList(2), match("I wait {int} second(s)./?", "I wait 2 seconds.")); - assertEquals(singletonList(1), match("I wait {int} second(s)./?", "I wait 1 second?")); - assertEquals(singletonList(1), match("I wait {int} second(s)./?", "I wait 1 second.")); } @Test - public void matches_alternative_in_optional_as_text() { + public void matches_alternation_in_optional_as_text() { assertEquals(emptyList(), match("three( brown/black) mice", "three brown/black mice")); } + @Test + public void does_not_allow_alternation_with_empty_alternative() { + Executable testMethod = () -> match("three brown//black mice", "three brown mice"); + CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); + assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: three brown//black mice"))); + } + + @Test + public void does_not_allow_optional_adjacent_to_alternation() { + Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); + CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); + assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: /black mice"))); + } + + + @Test public void matches_double_quoted_string() { @@ -212,20 +225,6 @@ public void does_not_allow_parameter_type_text_alternation() { } - @Test - public void does_not_allow_empty_alternative() { - Executable testMethod = () -> match("three brown//black mice", "three brown mice"); - CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: three brown//black mice"))); - } - - @Test - public void does_not_allow_optional_adjacent_to_alternative() { - Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); - CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: /black mice"))); - } - @Test public void exposes_source() { String expr = "I have {int} cuke(s)"; From 43256a162d3f2a2fd592a897ef8be62eae4ff304 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 26 Oct 2019 17:23:32 +0200 Subject: [PATCH 013/183] Additional tests pass --- .../go/cucumber_expression.go | 2 +- .../go/cucumber_expression_test.go | 38 +++++++++++++++++++ .../CucumberExpressionTest.java | 4 -- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 387afa12c2..c6fdd9c7fd 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -7,7 +7,7 @@ import ( "strings" ) -const alternativesMayNotBeEmpty = "Alternatives may not be empty: %s" +const alternativesMayNotBeEmpty = "Alternative may not be empty: %s" const parameterTypesCanNotBeAlternative = "Parameter types cannot be alternative: %s" const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 31845f6e24..f5ef50d5aa 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -30,6 +30,44 @@ func TestCucumberExpression(t *testing.T) { ) }) + t.Run("matches alternation", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "mice/rats and rats\\/mice", "rats and rats/mice"), + []interface{}{}, + ) + }) + + t.Run("matches optional before alternation", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three (brown )mice/rats", "three brown rats"), + []interface{}{}, + ) + }) + + t.Run("matches alternation in optional as text", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three( brown/black) mice", "three brown/black mice"), + []interface{}{}, + ) + }) + + t.Run("does not allow alternation with empty alternatives", func(t *testing.T) { + parameterTypeRegistry := NewParameterTypeRegistry() + _, err := NewCucumberExpression("three brown//black mice", parameterTypeRegistry) + require.Error(t, err) + require.Equal(t, "Alternative may not be empty: three brown//black mice", err.Error()) + }) + + t.Run("does not allow optional adjacent to alternation", func(t *testing.T) { + parameterTypeRegistry := NewParameterTypeRegistry() + _, err := NewCucumberExpression("three (brown)/black mice", parameterTypeRegistry) + require.Error(t, err) + require.Equal(t, "Alternative may not be empty: three (brown)/black mice", err.Error()) + }) + t.Run("matches double quoted string", func(t *testing.T) { require.Equal( t, diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 87ffbdd99c..d57562f769 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -70,9 +70,6 @@ public void does_not_allow_optional_adjacent_to_alternation() { assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: /black mice"))); } - - - @Test public void matches_double_quoted_string() { assertEquals(singletonList("blind"), match("three {string} mice", "three \"blind\" mice")); @@ -224,7 +221,6 @@ public void does_not_allow_parameter_type_text_alternation() { assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Parameter types cannot be alternative: {int}/x"))); } - @Test public void exposes_source() { String expr = "I have {int} cuke(s)"; From a4ef4bc4dc79bce96cc3eb0f3aa00ce874804649 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 26 Oct 2019 17:32:53 +0200 Subject: [PATCH 014/183] Remove BiFunction --- .../cucumberexpressions/CucumberExpression.java | 15 +++++++-------- .../CucumberExpressionTest.java | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 83bc81a9cb..5266872fad 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.regex.MatchResult; import java.util.regex.Matcher; @@ -70,7 +69,7 @@ private static String processEscapes(String text) { } private Function> processAlternation() { - return splitTextTokens(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, (expression, matchResult) -> { + return splitTextTokens(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, (matchResult) -> { // replace \/ with / // replace / with | String replacement = matchResult.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); @@ -82,11 +81,11 @@ private Function> processAlternation() { // Make sure the alternative parts aren't empty and don't contain parameter types String[] alternatives = replacement.split("\\|"); if (alternatives.length == 0) { - throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + expression); + throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } for (String alternative : alternatives) { if (alternative.isEmpty()) { - throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + expression); + throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } checkNotParameterType(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); } @@ -106,7 +105,7 @@ private void checkNotParameterType(String s, String message) { } private Function> processOptional() { - return splitTextTokens(OPTIONAL_PATTERN, (expression, matchResult) -> { + return splitTextTokens(OPTIONAL_PATTERN, (matchResult) -> { // look for double-escaped parentheses String parameterPart = matchResult.group(2); if (ESCAPE.equals(matchResult.group(1))) { @@ -119,7 +118,7 @@ private Function> processOptional() { } private Function> processParameters() { - return splitTextTokens(PARAMETER_PATTERN, (expression, matchResult) -> { + return splitTextTokens(PARAMETER_PATTERN, (matchResult) -> { String typeName = matchResult.group(2); if (ESCAPE.equals(matchResult.group(1))) { return new Token("{" + typeName + "}", Token.Type.TEXT); @@ -142,7 +141,7 @@ private String buildCaptureRegexp(List regexps) { .collect(joining(")|(?:", "((?:","))")); } - private static Function> splitTextTokens(Pattern pattern, BiFunction processor) { + private static Function> splitTextTokens(Pattern pattern, Function processor) { return token -> { if (token.type != Token.Type.TEXT) { return Stream.of(token); @@ -158,7 +157,7 @@ private static Function> splitTextTokens(Pattern pattern, B if (!prefix.isEmpty()) { tokens.add(new Token(prefix, Token.Type.TEXT)); } - tokens.add(processor.apply(expression, matcher.toMatchResult())); + tokens.add(processor.apply(matcher.toMatchResult())); previousEnd = end; } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index d57562f769..75ce1bf71b 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -67,7 +67,7 @@ public void does_not_allow_alternation_with_empty_alternative() { public void does_not_allow_optional_adjacent_to_alternation() { Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: /black mice"))); + assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: three (brown)/black mice"))); } @Test From ec587346c4e0f59431a205d2fb5696e035091770 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 26 Oct 2019 17:51:08 +0200 Subject: [PATCH 015/183] Naming --- cucumber-expressions/go/cucumber_expression.go | 13 ++++++------- .../cucumberexpressions/CucumberExpression.java | 13 ++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index c6fdd9c7fd..8ab41082d7 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -60,7 +60,7 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return nil, err } - pattern := result.escapeTextTokensAndJoin(tokens, "^", "$") + pattern := result.escapeRegexAndJoin(tokens, "^", "$") result.treeRegexp = NewTreeRegexp(regexp.MustCompile(pattern)) return result, nil } @@ -97,12 +97,12 @@ func (c *CucumberExpression) Source() string { return c.source } -func (c *CucumberExpression) escapeTextTokensAndJoin(expression []token, prefix string, suffix string) string { +func (c *CucumberExpression) escapeRegexAndJoin(expression []token, prefix string, suffix string) string { var builder strings.Builder builder.WriteString(prefix) for _, token := range expression { if token.tokenType == text { - builder.WriteString(c.processEscapes(token.text)) + builder.WriteString(c.escapeRegex(token.text)) } else { builder.WriteString(token.text) } @@ -111,8 +111,7 @@ func (c *CucumberExpression) escapeTextTokensAndJoin(expression []token, prefix return builder.String() } -func (c *CucumberExpression) processEscapes(expression string) string { - // This will cause explicitly-escaped parentheses to be double-escaped +func (c *CucumberExpression) escapeRegex(expression string) string { return escapeRegexp.ReplaceAllString(expression, `\$1`) } @@ -126,7 +125,7 @@ func (c *CucumberExpression) processOptional(expression []token) ([]token, error if parameterRegexp.MatchString(match) { err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeOptional, c.source)) } - optionalText := c.processEscapes(match[1 : len(match)-1]) + optionalText := c.escapeRegex(match[1 : len(match)-1]) return token{fmt.Sprintf("(?:%s)?", optionalText), optional} }) return result, err @@ -158,7 +157,7 @@ func (c *CucumberExpression) processAlteration(expression []token) ([]token, err if parameterRegexp.MatchString(alternative) { err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeAlternative, c.source)) } - alternativeTexts = append(alternativeTexts, c.processEscapes(alternative)) + alternativeTexts = append(alternativeTexts, c.escapeRegex(alternative)) } alternativeText := strings.Join(alternativeTexts, "|") return token{fmt.Sprintf("(?:%s)", alternativeText), alternation} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 5266872fad..b89584a792 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -53,18 +53,17 @@ private enum Type { .flatMap(processOptional()) .flatMap(processAlternation()) .flatMap(processParameters()) - .map(escapeTextTokens()) + .map(escapeRegex()) .collect(joining("", "^", "$")); treeRegexp = new TreeRegexp(pattern); } - private static Function escapeTextTokens() { - return token -> token.type != Token.Type.TEXT ? token.text : processEscapes(token.text); + private static Function escapeRegex() { + return token -> token.type != Token.Type.TEXT ? token.text : escapeRegex(token.text); } - private static String processEscapes(String text) { - // This will cause explicitly-escaped parentheses to be double-escaped + private static String escapeRegex(String text) { return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); } @@ -90,7 +89,7 @@ private Function> processAlternation() { checkNotParameterType(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); } String pattern = Arrays.stream(alternatives) - .map(CucumberExpression::processEscapes) + .map(CucumberExpression::escapeRegex) .collect(joining("|", "(?:", ")")); return new Token(pattern, Token.Type.ALTERNATION); @@ -113,7 +112,7 @@ private Function> processOptional() { } checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - return new Token("(?:" + processEscapes(parameterPart) + ")?", Token.Type.OPTIONAL); + return new Token("(?:" + escapeRegex(parameterPart) + ")?", Token.Type.OPTIONAL); }); } From 291eb0f9ed9d0ad2f9333dffd6cf8bad2aa40afa Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 13:23:19 +0100 Subject: [PATCH 016/183] Handle doubly escaped slashes --- cucumber-expressions/README.md | 18 +++++++++ .../go/cucumber_expression.go | 39 ++++++++++++++----- .../go/cucumber_expression_test.go | 15 +++++++ cucumber-expressions/java/README.md | 17 -------- .../CucumberExpression.java | 22 +++++++---- .../CucumberExpressionTest.java | 10 +++++ 6 files changed, 86 insertions(+), 35 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index bfaef63d31..3400ab1406 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -1,5 +1,22 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for more details. + +## Grammar ## + +``` +cucumber-expression := [ optional | escaped-optional | other-then-optional ]* +optional := `\\`? + '(' + text + ')' +escaped-optional := '\(' + other-then-optional + ')' +other-then-optional: = [ alternative | escaped-alternative | other-then-alternative ]* +alternative := text + [ `\\`? + '/' + text ]+ +escaped-alternative := other-then-alternative +[ '\/' + other-then-alternative ]+ +other-then-alternative := [ parameter | escaped-parameter | other-then-parameter ]* +parameter := '{' + text + '}' +escaped-parameter := `\\`? + '\{' + other-then-parameter + '}' +other-then-parameter:= text +text := .* +``` + ## Acknowledgements The Cucumber Expression syntax is inspired by similar expression syntaxes in @@ -7,3 +24,4 @@ other BDD tools, such as [Turnip](https://github.com/jnicklas/turnip), [Behat](h Big thanks to Jonas Nicklas, Konstantin Kudryashov and Jens Engel for implementing those libraries. + diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 8ab41082d7..8a3543196c 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -12,11 +12,12 @@ const parameterTypesCanNotBeAlternative = "Parameter types cannot be alternative const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" var escapeRegexp = regexp.MustCompile(`([\\^\[({$.|?*+})\]])`) -var parameterRegexp = regexp.MustCompile(`(\\)?{([^}]*)}`) -var optionalRegexp = regexp.MustCompile(`(\\)?\([^)]+\)`) +var parameterRegexp = regexp.MustCompile(`((?:\\){0,2}){([^}]*)}`) +var optionalRegexp = regexp.MustCompile(`((?:\\){0,2})\([^)]+\)`) var alternativeNonWhitespaceTextRegexp = regexp.MustCompile(`([^\s/]*)((/[^\s/]*)+)`) -const doubleEscape = `\` +const singleEscape = `\` +const doubleEscape = `\\` type tokenType int @@ -118,15 +119,24 @@ func (c *CucumberExpression) escapeRegex(expression string) string { func (c *CucumberExpression) processOptional(expression []token) ([]token, error) { var err error result := splitTextTokens(expression, optionalRegexp, func(match string) (token) { - // look for double-escaped parentheses - if strings.HasPrefix(match, doubleEscape) { + // look for single-escaped parentheses + if !strings.HasPrefix(match, doubleEscape) && strings.HasPrefix(match, singleEscape) { return token{fmt.Sprintf(`(%s)`, match[2:len(match)-1]), text} } if parameterRegexp.MatchString(match) { err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeOptional, c.source)) } - optionalText := c.escapeRegex(match[1 : len(match)-1]) - return token{fmt.Sprintf("(?:%s)?", optionalText), optional} + + var optionalText string + var escape string + if strings.HasPrefix(match, doubleEscape) { + optionalText = c.escapeRegex(match[3 : len(match)-1]) + escape = doubleEscape + } else { + optionalText = c.escapeRegex(match[1 : len(match)-1]) + escape = "" + } + return token{fmt.Sprintf("%s(?:%s)?", escape, optionalText), optional} }) return result, err } @@ -168,10 +178,19 @@ func (c *CucumberExpression) processAlteration(expression []token) ([]token, err func (c *CucumberExpression) processParameters(expression []token, parameterTypeRegistry *ParameterTypeRegistry) ([]token, error) { var err error result := splitTextTokens(expression, parameterRegexp, func(match string) token { - if strings.HasPrefix(match, doubleEscape) { + // look for single-escaped parentheses + if !strings.HasPrefix(match, doubleEscape) && strings.HasPrefix(match, singleEscape) { return token{fmt.Sprintf(`{%s}`, match[5:len(match)-1]), text} } - typeName := match[1 : len(match)-1] + var typeName string + var escape string + if strings.HasPrefix(match, doubleEscape) { + typeName = match[3 : len(match)-1] + escape = doubleEscape + } else { + typeName = match[1 : len(match)-1] + escape = "" + } err = CheckParameterTypeName(typeName) if err != nil { return token{match, parameter} @@ -182,7 +201,7 @@ func (c *CucumberExpression) processParameters(expression []token, parameterType return token{match, parameter} } c.parameterTypes = append(c.parameterTypes, parameterType) - return token{buildCaptureRegexp(parameterType.regexps), parameter} + return token{escape + buildCaptureRegexp(parameterType.regexps), parameter} }) return result, err } diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index f5ef50d5aa..bd385aa87e 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -179,6 +179,14 @@ func TestCucumberExpression(t *testing.T) { ) }) + t.Run("matches doubly escaped parenthesis", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three \\\\(exceptionally) \\\\{string} mice", `three \exceptionally \"blind" mice`), + []interface{}{"blind"}, + ) + }) + t.Run("matches escaped slash", func(t *testing.T) { require.Equal( t, @@ -186,6 +194,13 @@ func TestCucumberExpression(t *testing.T) { []interface{}{}, ) }) + t.Run("matches doubly escaped slash", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "12\\\\/2020", `12\/2020`), + []interface{}{}, + ) + }) t.Run("matches int", func(t *testing.T) { require.Equal( diff --git a/cucumber-expressions/java/README.md b/cucumber-expressions/java/README.md index c654eceff6..1515320468 100644 --- a/cucumber-expressions/java/README.md +++ b/cucumber-expressions/java/README.md @@ -3,20 +3,3 @@ # Cucumber Expressions for Java [The docs are here](https://cucumber.io/docs/cucumber/cucumber-expressions/). - - -## Grammar ## - -``` -cucumber-expression := [ optional | escaped-optional | other-then-optional ]* -optional := '(' + text + ')' -escaped-optional := '\(' + other-then-optional + ')' -other-then-optional: = [ alternative | escaped-alternative | other-then-alternative ]* -alternative := text + [ '/' + text ]+ -escaped-alternative := other-then-alternative +[ '\/' + other-then-alternative ]+ -other-then-alternative := [ parameter | escaped-parameter | other-then-parameter ]* -parameter := '{' + text + '}' -escaped-parameter := '\{' + other-then-parameter + '}' -other-then-parameter:= text -text := .* -``` \ No newline at end of file diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index b89584a792..ec7e89653a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -18,10 +18,9 @@ public final class CucumberExpression implements Expression { private static final Pattern ESCAPE_PATTERN = Pattern.compile("([\\\\^\\[({$.|?*+})\\]])"); @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces - static final Pattern PARAMETER_PATTERN = Pattern.compile("(\\\\)?\\{([^}]*)\\}"); - private static final Pattern OPTIONAL_PATTERN = Pattern.compile("(\\\\)?\\(([^)]+)\\)"); + static final Pattern PARAMETER_PATTERN = Pattern.compile("((?:\\\\){0,2})\\{([^}]*)\\}"); + private static final Pattern OPTIONAL_PATTERN = Pattern.compile("((?:\\\\){0,2})\\(([^)]+)\\)"); private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s/]*)((/[^\\s/]*)+)"); - private static final String ESCAPE = "\\"; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; private static final String ALTERNATIVE_MAY_NOT_BE_EMPTY = "Alternative may not be empty: "; @@ -105,21 +104,26 @@ private void checkNotParameterType(String s, String message) { private Function> processOptional() { return splitTextTokens(OPTIONAL_PATTERN, (matchResult) -> { - // look for double-escaped parentheses String parameterPart = matchResult.group(2); - if (ESCAPE.equals(matchResult.group(1))) { + String escapes = matchResult.group(1); + // look for single-escaped parentheses + if (escapes.length() == 1) { return new Token("(" + parameterPart + ")", Token.Type.TEXT); } checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - return new Token("(?:" + escapeRegex(parameterPart) + ")?", Token.Type.OPTIONAL); + String pattern = "(?:" + escapeRegex(parameterPart) + ")?"; + // either no or double escape + return new Token(escapes + pattern, Token.Type.OPTIONAL); }); } private Function> processParameters() { return splitTextTokens(PARAMETER_PATTERN, (matchResult) -> { String typeName = matchResult.group(2); - if (ESCAPE.equals(matchResult.group(1))) { + String escape = matchResult.group(1); + // look for single-escaped parentheses + if (escape.length() == 1) { return new Token("{" + typeName + "}", Token.Type.TEXT); } ParameterType.checkParameterTypeName(typeName); @@ -128,7 +132,9 @@ private Function> processParameters() { throw new UndefinedParameterTypeException(typeName); } parameterTypes.add(parameterType); - return new Token(buildCaptureRegexp(parameterType.getRegexps()), Token.Type.PARAMETER); + String pattern = buildCaptureRegexp(parameterType.getRegexps()); + // either no or double escape + return new Token(escape + pattern, Token.Type.PARAMETER); }); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 75ce1bf71b..bb9a6945bb 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -140,11 +140,21 @@ public void matches_escaped_parenthesis() { assertEquals(singletonList("blind"), match("three \\(exceptionally) {string} mice", "three (exceptionally) \"blind\" mice")); } + @Test + public void matches_doubly_escaped_parenthesis() { + assertEquals(singletonList("blind"), match("three \\\\(exceptionally) \\\\{string} mice", "three \\exceptionally \\\"blind\" mice")); + } + @Test public void matches_escaped_slash() { assertEquals(emptyList(), match("12\\/2020", "12/2020")); } + @Test + public void matches_doubly_escaped_slash() { + assertEquals(emptyList(), match("12\\\\/2020", "12\\/2020")); + } + @Test public void matches_int() { assertEquals(singletonList(22), match("{int}", "22")); From aaa4ac05bdf84278108a26bbb766ed4c9dcf469d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 13:43:12 +0100 Subject: [PATCH 017/183] Naming --- .../CucumberExpression.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index ec7e89653a..2356c56ec3 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -67,10 +67,13 @@ private static String escapeRegex(String text) { } private Function> processAlternation() { - return splitTextTokens(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, (matchResult) -> { + return splitTextTokens(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, (match) -> { // replace \/ with / // replace / with | - String replacement = matchResult.group(0).replace('/', '|').replaceAll("\\\\\\|", "/"); + String replacement = match.group(0) + .replace('/', '|') + .replaceAll("\\\\\\|", "/"); + if (!replacement.contains("|")) { // All / were escaped return new Token(replacement, Token.Type.TEXT); @@ -103,9 +106,9 @@ private void checkNotParameterType(String s, String message) { } private Function> processOptional() { - return splitTextTokens(OPTIONAL_PATTERN, (matchResult) -> { - String parameterPart = matchResult.group(2); - String escapes = matchResult.group(1); + return splitTextTokens(OPTIONAL_PATTERN, (match) -> { + String parameterPart = match.group(2); + String escapes = match.group(1); // look for single-escaped parentheses if (escapes.length() == 1) { return new Token("(" + parameterPart + ")", Token.Type.TEXT); @@ -119,9 +122,9 @@ private Function> processOptional() { } private Function> processParameters() { - return splitTextTokens(PARAMETER_PATTERN, (matchResult) -> { - String typeName = matchResult.group(2); - String escape = matchResult.group(1); + return splitTextTokens(PARAMETER_PATTERN, (match) -> { + String typeName = match.group(2); + String escape = match.group(1); // look for single-escaped parentheses if (escape.length() == 1) { return new Token("{" + typeName + "}", Token.Type.TEXT); From c084b171fb5d15e761a59a9aaf4374eba2c13090 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 14:05:21 +0100 Subject: [PATCH 018/183] Use FindAllStringSubmatch to remove some magic numbers --- .../go/cucumber_expression.go | 74 ++++++++----------- .../CucumberExpression.java | 6 +- 2 files changed, 32 insertions(+), 48 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 8a3543196c..514eb7a76a 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -13,12 +13,9 @@ const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" var escapeRegexp = regexp.MustCompile(`([\\^\[({$.|?*+})\]])`) var parameterRegexp = regexp.MustCompile(`((?:\\){0,2}){([^}]*)}`) -var optionalRegexp = regexp.MustCompile(`((?:\\){0,2})\([^)]+\)`) +var optionalRegexp = regexp.MustCompile(`((?:\\){0,2})\(([^)]+)\)`) var alternativeNonWhitespaceTextRegexp = regexp.MustCompile(`([^\s/]*)((/[^\s/]*)+)`) -const singleEscape = `\` -const doubleEscape = `\\` - type tokenType int const ( @@ -51,7 +48,7 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return nil, err } - tokens, err = result.processAlteration(tokens) + tokens, err = result.processAlternation(tokens) if err != nil { return nil, err } @@ -118,35 +115,28 @@ func (c *CucumberExpression) escapeRegex(expression string) string { func (c *CucumberExpression) processOptional(expression []token) ([]token, error) { var err error - result := splitTextTokens(expression, optionalRegexp, func(match string) (token) { + result := splitTextTokens(expression, optionalRegexp, func(match []string) (token) { // look for single-escaped parentheses - if !strings.HasPrefix(match, doubleEscape) && strings.HasPrefix(match, singleEscape) { - return token{fmt.Sprintf(`(%s)`, match[2:len(match)-1]), text} + optionalPart := match[2] + escapes := match[1] + if len(escapes) == 1 { + return token{fmt.Sprintf(`(%s)`, optionalPart), text} } - if parameterRegexp.MatchString(match) { + if parameterRegexp.MatchString(optionalPart) { err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeOptional, c.source)) } - - var optionalText string - var escape string - if strings.HasPrefix(match, doubleEscape) { - optionalText = c.escapeRegex(match[3 : len(match)-1]) - escape = doubleEscape - } else { - optionalText = c.escapeRegex(match[1 : len(match)-1]) - escape = "" - } - return token{fmt.Sprintf("%s(?:%s)?", escape, optionalText), optional} + // either no or double escape + return token{fmt.Sprintf("%s(?:%s)?", escapes, optionalPart), optional} }) return result, err } -func (c *CucumberExpression) processAlteration(expression []token) ([]token, error) { +func (c *CucumberExpression) processAlternation(expression []token) ([]token, error) { var err error - result := splitTextTokens(expression, alternativeNonWhitespaceTextRegexp, func(match string) token { + result := splitTextTokens(expression, alternativeNonWhitespaceTextRegexp, func(match []string) token { // replace \/ with / // replace / with | - replacement := strings.Replace(match, "/", "|", -1) + replacement := strings.Replace(match[0], "/", "|", -1) replacement = strings.Replace(replacement, `\|`, "/", -1) if !strings.Contains(replacement, "|") { @@ -156,7 +146,7 @@ func (c *CucumberExpression) processAlteration(expression []token) ([]token, err // Make sure the alternative alternatives aren't empty and don't contain parameter types alternatives := strings.Split(replacement, "|") - alternativeTexts := make([]string,0) + alternativeTexts := make([]string, 0) if len(alternatives) == 0 { err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) } @@ -177,36 +167,30 @@ func (c *CucumberExpression) processAlteration(expression []token) ([]token, err func (c *CucumberExpression) processParameters(expression []token, parameterTypeRegistry *ParameterTypeRegistry) ([]token, error) { var err error - result := splitTextTokens(expression, parameterRegexp, func(match string) token { + result := splitTextTokens(expression, parameterRegexp, func(match []string) token { + typeName := match[2] + escapes := match[1] // look for single-escaped parentheses - if !strings.HasPrefix(match, doubleEscape) && strings.HasPrefix(match, singleEscape) { + if len(escapes) == 1 { return token{fmt.Sprintf(`{%s}`, match[5:len(match)-1]), text} } - var typeName string - var escape string - if strings.HasPrefix(match, doubleEscape) { - typeName = match[3 : len(match)-1] - escape = doubleEscape - } else { - typeName = match[1 : len(match)-1] - escape = "" - } err = CheckParameterTypeName(typeName) if err != nil { - return token{match, parameter} + return token{typeName, parameter} } parameterType := parameterTypeRegistry.LookupByTypeName(typeName) if parameterType == nil { err = NewUndefinedParameterTypeError(typeName) - return token{match, parameter} + return token{typeName, parameter} } c.parameterTypes = append(c.parameterTypes, parameterType) - return token{escape + buildCaptureRegexp(parameterType.regexps), parameter} + // either no or double escape + return token{escapes + buildCaptureRegexp(parameterType.regexps), parameter} }) return result, err } -func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func(string) token) []token { +func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func([]string) token) []token { newTokens := make([]token, 0) for _, t := range tokens { if t.tokenType != text { @@ -214,17 +198,17 @@ func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func(strin continue } expression := t.text - loc := regexp.FindAllStringIndex(expression, -1) + locations := regexp.FindAllStringIndex(expression, -1) + groups := regexp.FindAllStringSubmatch(expression, -1) previousEnd := 0 - for i := 0; i < len(loc); i++ { - start := loc[i][0] - end := loc[i][1] + for i := 0; i < len(locations); i++ { + start := locations[i][0] + end := locations[i][1] prefix := expression[previousEnd:start] if len(prefix) > 0 { newTokens = append(newTokens, token{prefix, text}) } - match := expression[start:end] - newTokens = append(newTokens, processor(match)) + newTokens = append(newTokens, processor(groups[i])) previousEnd = end } suffix := expression[previousEnd:] diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 2356c56ec3..2c7c07780f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -124,9 +124,9 @@ private Function> processOptional() { private Function> processParameters() { return splitTextTokens(PARAMETER_PATTERN, (match) -> { String typeName = match.group(2); - String escape = match.group(1); + String escapes = match.group(1); // look for single-escaped parentheses - if (escape.length() == 1) { + if (escapes.length() == 1) { return new Token("{" + typeName + "}", Token.Type.TEXT); } ParameterType.checkParameterTypeName(typeName); @@ -137,7 +137,7 @@ private Function> processParameters() { parameterTypes.add(parameterType); String pattern = buildCaptureRegexp(parameterType.getRegexps()); // either no or double escape - return new Token(escape + pattern, Token.Type.PARAMETER); + return new Token(escapes + pattern, Token.Type.PARAMETER); }); } From 417c564cd3a4006b15be1c93f3571a5b79c314bf Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 14:21:51 +0100 Subject: [PATCH 019/183] Use fixed size slices --- cucumber-expressions/go/cucumber_expression.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 514eb7a76a..ce145a0fd2 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -40,8 +40,7 @@ type CucumberExpression struct { func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (Expression, error) { result := &CucumberExpression{source: expression, parameterTypeRegistry: parameterTypeRegistry} - tokens := make([]token, 0) - tokens = append(tokens, token{expression, text}) + tokens := []token{{expression, text}} tokens, err := result.processOptional(tokens) if err != nil { @@ -146,18 +145,18 @@ func (c *CucumberExpression) processAlternation(expression []token) ([]token, er // Make sure the alternative alternatives aren't empty and don't contain parameter types alternatives := strings.Split(replacement, "|") - alternativeTexts := make([]string, 0) if len(alternatives) == 0 { err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) } - for _, alternative := range alternatives { + alternativeTexts := make([]string, len(alternatives)) + for i, alternative := range alternatives { if len(alternative) == 0 { err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) } if parameterRegexp.MatchString(alternative) { err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeAlternative, c.source)) } - alternativeTexts = append(alternativeTexts, c.escapeRegex(alternative)) + alternativeTexts[i] = c.escapeRegex(alternative) } alternativeText := strings.Join(alternativeTexts, "|") return token{fmt.Sprintf("(?:%s)", alternativeText), alternation} @@ -191,7 +190,9 @@ func (c *CucumberExpression) processParameters(expression []token, parameterType } func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func([]string) token) []token { - newTokens := make([]token, 0) + // Guesstimate: When a match is found this splitTextTokens will at a minimum + // create 2 additional tokens + newTokens := make([]token, 0, len(tokens)+2) for _, t := range tokens { if t.tokenType != text { newTokens = append(newTokens, t) From 9bb8fb7a9581a8c48ddccf4d4839100832ed7eab Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 14:28:14 +0100 Subject: [PATCH 020/183] Faster guesstimate --- cucumber-expressions/go/cucumber_expression.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index ce145a0fd2..75101ca78f 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -191,8 +191,9 @@ func (c *CucumberExpression) processParameters(expression []token, parameterType func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func([]string) token) []token { // Guesstimate: When a match is found this splitTextTokens will at a minimum - // create 2 additional tokens - newTokens := make([]token, 0, len(tokens)+2) + // create 2 additional tokens. Adding 8 additional capacity allows for a few + // more + newTokens := make([]token, 0, len(tokens)+8) for _, t := range tokens { if t.tokenType != text { newTokens = append(newTokens, t) From eac0e514cc42f7a17a482d02ac72e9234aaaafc2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 14:42:29 +0100 Subject: [PATCH 021/183] Fix --- cucumber-expressions/go/cucumber_expression.go | 2 +- cucumber-expressions/go/cucumber_expression_test.go | 4 ++-- .../cucumber/cucumberexpressions/CucumberExpressionTest.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 75101ca78f..4c5d0c9936 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -171,7 +171,7 @@ func (c *CucumberExpression) processParameters(expression []token, parameterType escapes := match[1] // look for single-escaped parentheses if len(escapes) == 1 { - return token{fmt.Sprintf(`{%s}`, match[5:len(match)-1]), text} + return token{fmt.Sprintf(`{%s}`, typeName), text} } err = CheckParameterTypeName(typeName) if err != nil { diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index bd385aa87e..6b37700cb0 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -174,8 +174,8 @@ func TestCucumberExpression(t *testing.T) { t.Run("matches escaped parenthesis", func(t *testing.T) { require.Equal( t, - MatchCucumberExpression(t, "three \\(exceptionally) {string} mice", `three (exceptionally) "blind" mice`), - []interface{}{"blind"}, + MatchCucumberExpression(t, "three \\(exceptionally) \\{string} mice", `three (exceptionally) {string} mice`), + []interface{}{}, ) }) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index bb9a6945bb..05d58d87ca 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -137,7 +137,7 @@ public void matches_double_quoted_empty_string_as_empty_string_along_with_other_ @Test public void matches_escaped_parenthesis() { - assertEquals(singletonList("blind"), match("three \\(exceptionally) {string} mice", "three (exceptionally) \"blind\" mice")); + assertEquals(emptyList(), match("three \\(exceptionally) \\{string} mice", "three (exceptionally) {string} mice")); } @Test From 2a9006b7893432b18050a372b6f363f3236e6a9a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 14:52:13 +0100 Subject: [PATCH 022/183] Remove redundant empty checks --- cucumber-expressions/go/cucumber_expression.go | 8 ++------ .../cucumberexpressions/CucumberExpression.java | 13 ++++--------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 4c5d0c9936..184243f1b2 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -207,16 +207,12 @@ func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func([]str start := locations[i][0] end := locations[i][1] prefix := expression[previousEnd:start] - if len(prefix) > 0 { - newTokens = append(newTokens, token{prefix, text}) - } + newTokens = append(newTokens, token{prefix, text}) newTokens = append(newTokens, processor(groups[i])) previousEnd = end } suffix := expression[previousEnd:] - if len(suffix) > 0 { - newTokens = append(newTokens, token{suffix, text}) - } + newTokens = append(newTokens, token{suffix, text}) } return newTokens } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 2c7c07780f..9ad2853e0b 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -143,10 +143,10 @@ private Function> processParameters() { private String buildCaptureRegexp(List regexps) { if (regexps.size() == 1) { - return "(" + regexps.get(0) + ")"; + return "(" + regexps.get(0) + ")"; } return regexps.stream() - .collect(joining(")|(?:", "((?:","))")); + .collect(joining(")|(?:", "((?:", "))")); } private static Function> splitTextTokens(Pattern pattern, Function processor) { @@ -162,17 +162,12 @@ private static Function> splitTextTokens(Pattern pattern, F int start = matcher.start(); int end = matcher.end(); String prefix = expression.substring(previousEnd, start); - if (!prefix.isEmpty()) { - tokens.add(new Token(prefix, Token.Type.TEXT)); - } + tokens.add(new Token(prefix, Token.Type.TEXT)); tokens.add(processor.apply(matcher.toMatchResult())); previousEnd = end; } - String suffix = expression.substring(previousEnd); - if (!suffix.isEmpty()) { - tokens.add(new Token(suffix, Token.Type.TEXT)); - } + tokens.add(new Token(suffix, Token.Type.TEXT)); return tokens.stream(); }; } From b7b1333427bd246f77dc069b45e904df789a1f04 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 15:14:10 +0100 Subject: [PATCH 023/183] Replace sprintf with concat --- cucumber-expressions/go/cucumber_expression.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 184243f1b2..1c0f716016 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -119,13 +119,13 @@ func (c *CucumberExpression) processOptional(expression []token) ([]token, error optionalPart := match[2] escapes := match[1] if len(escapes) == 1 { - return token{fmt.Sprintf(`(%s)`, optionalPart), text} + return token{`(` + optionalPart + `)`, text} } if parameterRegexp.MatchString(optionalPart) { err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeOptional, c.source)) } // either no or double escape - return token{fmt.Sprintf("%s(?:%s)?", escapes, optionalPart), optional} + return token{escapes + "(?:" + optionalPart + ")?", optional} }) return result, err } @@ -159,7 +159,7 @@ func (c *CucumberExpression) processAlternation(expression []token) ([]token, er alternativeTexts[i] = c.escapeRegex(alternative) } alternativeText := strings.Join(alternativeTexts, "|") - return token{fmt.Sprintf("(?:%s)", alternativeText), alternation} + return token{"(?:" + alternativeText + ")", alternation} }) return result, err } @@ -171,7 +171,7 @@ func (c *CucumberExpression) processParameters(expression []token, parameterType escapes := match[1] // look for single-escaped parentheses if len(escapes) == 1 { - return token{fmt.Sprintf(`{%s}`, typeName), text} + return token{"{" + typeName + "}", text} } err = CheckParameterTypeName(typeName) if err != nil { @@ -219,15 +219,15 @@ func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func([]str func buildCaptureRegexp(regexps []*regexp.Regexp) string { if len(regexps) == 1 { - return fmt.Sprintf("(%s)", regexps[0].String()) + return "(" + regexps[0].String() + ")" } captureGroups := make([]string, len(regexps)) for i, r := range regexps { - captureGroups[i] = fmt.Sprintf("(?:%s)", r.String()) + captureGroups[i] = "(?:" + r.String() + ")" } - return fmt.Sprintf("(%s)", strings.Join(captureGroups, "|")) + return "(" + strings.Join(captureGroups, "|") + ")" } func (r *CucumberExpression) objectMapperTransformer(typeHint reflect.Type) func(args ...*string) interface{} { From f4d96db2b3a80e3aa124d8afd13f37abd152f26f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 27 Oct 2019 15:17:15 +0100 Subject: [PATCH 024/183] Fix --- cucumber-expressions/go/cucumber_expression.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 1c0f716016..1280d239d1 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -115,9 +115,9 @@ func (c *CucumberExpression) escapeRegex(expression string) string { func (c *CucumberExpression) processOptional(expression []token) ([]token, error) { var err error result := splitTextTokens(expression, optionalRegexp, func(match []string) (token) { - // look for single-escaped parentheses optionalPart := match[2] escapes := match[1] + // look for single-escaped parentheses if len(escapes) == 1 { return token{`(` + optionalPart + `)`, text} } From 0e7d713398bfcecdeed092d02caff54188ca47a6 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 31 Oct 2019 17:19:50 +0100 Subject: [PATCH 025/183] Implement LR(1) parser --- .../CucumberExpressionParser.java | 242 ++++++++++++++++++ .../CucumberExpressionTokenizer.java | 158 ++++++++++++ .../CucumberExpressionParserTest.java | 198 ++++++++++++++ .../CucumberExpressionTokenizerTest.java | 142 ++++++++++ 4 files changed, 740 insertions(+) create mode 100644 cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java create mode 100644 cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java create mode 100644 cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java create mode 100644 cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java new file mode 100644 index 0000000000..35a0c76c1f --- /dev/null +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -0,0 +1,242 @@ +package io.cucumber.cucumberexpressions; + +import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.joining; + +final class CucumberExpressionParser { + + private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + + private static final List parsers = asList( + parseBetween(Optional::new, BEGIN_OPTIONAL, END_OPTIONAL), + parseAlternation(), + parseBetween(Parameter::new, BEGIN_PARAMETER, END_PARAMETER), + parseText() + ); + + private static Parse parseText() { + return (ast, expression, current) -> { + ast.add(new Text(singletonList(expression.get(current)))); + return 1; + }; + } + + private static Parse parseAlternation() { + return (ast, expression, current) -> { + Token token = expression.get(current); + if (token.type == WHITE_SPACE) { + return 0; + } + + int pivot = findFirst(expression, current, ALTERNATION); + if (pivot < 0) { + return 0; + } + + boolean contiguous = expression + .subList(current, pivot) + .stream() + .noneMatch(t -> t.type == WHITE_SPACE); + if (!contiguous) { + return 0; + } + + int endToken = findFirst(expression, pivot, WHITE_SPACE); + if (endToken < 0) { + endToken = expression.size(); + } + + ast.add(new Alternation(expression.subList(current, endToken))); + return endToken - current; + }; + } + + private static Parse parseBetween(Function, Node> createNode, Token.Type startToken, Token.Type endToken) { + return (ast, expression, current) -> { + Token token = expression.get(current); + if (token.type != startToken) { + return 0; + } + + int endIndex = findFirst(expression, current + 1, endToken); + if (endIndex > 0) { + ast.add(createNode.apply(expression.subList(current + 1, endIndex))); + return endIndex - current + 1; + } + + return 0; + }; + } + + private static int findFirst(List expression, int fromIndex, Token.Type end) { + for (int i = fromIndex; i < expression.size(); i++) { + Token candidate = expression.get(i); + if (candidate.type == end) { + return i; + } + } + return -1; + } + + + private interface Parse { + + int parse(List ast, List expression, int current); + + } + + static abstract class Node { + final List tokens; + + Node(List tokens) { + this.tokens = tokens; + } + + } + + + static final class Text extends Node { + + Text(List tokens) { + super(tokens); + } + + @Override + public String toString() { + return tokens.stream().map(token -> { + switch (token.type) { + case ESCAPED_ALTERNATION: + return "/"; + default: + return token.text; + } + }).collect(joining()); + } + } + + static final class Optional extends Node { + + Optional(List tokens) { + super(tokens); + } + + @Override + public String toString() { + return tokens.stream() + .map(token -> { + switch (token.type) { + case ESCAPED_BEGIN_OPTIONAL: + return "("; + case ESCAPED_END_OPTIONAL: + return ")"; + default: + return token.text; + } + }) + .collect(joining()); + } + } + + static final class Parameter extends Node { + + Parameter(List tokens) { + super(tokens); + } + + @Override + public String toString() { + return tokens.stream() + .map(token -> { + switch (token.type) { + case ESCAPED_BEGIN_PARAMETER: + return "{"; + case ESCAPED_END_PARAMETER: + return "}"; + default: + return token.text; + } + + }) + .collect(joining()); + } + } + + static final class Alternation extends Node { + + private final ArrayList> alternatives; + + Alternation(List tokens) { + super(tokens); + if (tokens.isEmpty()) { + throw new IllegalArgumentException("" + tokens.size()); + } + this.alternatives = new ArrayList<>(); + List alternative = new ArrayList<>(); + alternatives.add(alternative); + + for (Token token : tokens) { + if (token.type == ALTERNATION) { + alternative = new ArrayList<>(); + alternatives.add(alternative); + } else { + alternative.add(token); + } + } + } + + @Override + public String toString() { + return alternatives.stream() + .map(alternatives -> alternatives + .stream() + .map(token -> { + switch (token.type) { + case ESCAPED_WHITE_SPACE: + return " "; + case ESCAPED_ALTERNATION: + return "/"; + default: + return token.text; + } + }).collect(joining())) + .collect(joining(" - ")); + } + + } + + List parse(String expressionStr) { + List tokens = tokenizer.tokenize(expressionStr); + List ast = new ArrayList<>(); + int length = tokens.size(); + int current = 0; + while (current < length) { + boolean parsed = false; + for (Parse parser : parsers) { + int consumedChars = parser.parse(ast, tokens, current); + if (consumedChars != 0) { + current += consumedChars; + parsed = true; + break; + } + } + if (!parsed) { + // Should not happen + throw new IllegalStateException("Could not parse " + tokens); + } + } + return ast; + } +} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java new file mode 100644 index 0000000000..83ec434584 --- /dev/null +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -0,0 +1,158 @@ +package io.cucumber.cucumberexpressions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class CucumberExpressionTokenizer { + + private static final List tokenizers = Arrays.asList( + tokenizePattern(Token.Type.ESCAPED_WHITE_SPACE, Pattern.compile("\\\\\\s")), + tokenizePattern(Token.Type.WHITE_SPACE, Pattern.compile("\\s+")), + + tokenizeString(Token.Type.ESCAPED_BEGIN_OPTIONAL, "\\("), + tokenizeCharacter(Token.Type.BEGIN_OPTIONAL, '('), + + tokenizeString(Token.Type.ESCAPED_END_OPTIONAL, "\\)"), + tokenizeCharacter(Token.Type.END_OPTIONAL, ')'), + + tokenizeString(Token.Type.ESCAPED_BEGIN_PARAMETER, "\\{"), + tokenizeCharacter(Token.Type.BEGIN_PARAMETER, '{'), + + tokenizeString(Token.Type.ESCAPED_END_PARAMETER, "\\}"), + tokenizeCharacter(Token.Type.END_PARAMETER, '}'), + + tokenizeString(Token.Type.ESCAPED_ALTERNATION, "\\/"), + tokenizeCharacter(Token.Type.ALTERNATION, '/'), + + tokenizeString(Token.Type.ESCAPED_ESCAPE, "\\\\"), + tokenizeString(Token.Type.ESCAPE, "\\"), + + tokenizePattern(Token.Type.TEXT, Pattern.compile("[^(){}\\\\/\\s]+")) + ); + + List tokenize(String expression) { + List tokens = new ArrayList<>(); + int length = expression.length(); + int current = 0; + while (current < length) { + boolean tokenized = false; + for (Tokenize tokenizer : tokenizers) { + int consumedChars = tokenizer.tokenize(tokens, expression, current); + if (consumedChars != 0) { + current += consumedChars; + tokenized = true; + break; + } + } + if (!tokenized) { + // Should not happen + throw new IllegalStateException("Could not parse " + expression); + } + } + return tokens; + } + + private static Tokenize tokenizeCharacter(Token.Type type, char character) { + return (tokens, expression, current) -> { + char c = expression.charAt(current); + if (character != c) { + return 0; + } + Token e = new Token("" + character, type); + tokens.add(e); + return 1; + }; + } + + private static Tokenize tokenizeString(Token.Type type, String string) { + return (tokens, expression, current) -> { + boolean c = expression.regionMatches(current, string, 0, string.length()); + if (!c) { + return 0; + } + tokens.add(new Token(string, type)); + return string.length(); + }; + } + + private static Tokenize tokenizePattern(Token.Type type, Pattern pattern) { + return (tokens, expression, current) -> { + String tail = expression.substring(current); + Matcher matcher = pattern.matcher(tail); + if (!matcher.lookingAt()) { + return 0; + } + String match = tail.substring(0, matcher.end()); + tokens.add(new Token(match, type)); + return match.length(); + }; + } + + + private interface Tokenize { + int tokenize(List tokens, String expression, int current); + } + + static class Token { + final String text; + final Type type; + + Token(String text, Type type) { + this.text = text; + this.type = type; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Token token = (Token) o; + return text.equals(token.text) && + type == token.type; + } + + @Override + public int hashCode() { + return Objects.hash(text, type); + } + + @Override + public String toString() { + return "Token{" + + "text='" + text + '\'' + + ", type=" + type + + '}'; + } + + enum Type { + + ESCAPED_WHITE_SPACE, + WHITE_SPACE, + + ESCAPED_BEGIN_OPTIONAL, + BEGIN_OPTIONAL, + + ESCAPED_END_OPTIONAL, + END_OPTIONAL, + + ESCAPED_BEGIN_PARAMETER, + BEGIN_PARAMETER, + + ESCAPED_END_PARAMETER, + END_PARAMETER, + + ESCAPED_ALTERNATION, + ALTERNATION, + + ESCAPED_ESCAPE, + ESCAPE, + + TEXT; + } + } +} diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java new file mode 100644 index 0000000000..758d334d9f --- /dev/null +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -0,0 +1,198 @@ +package io.cucumber.cucumberexpressions; + +import io.cucumber.cucumberexpressions.CucumberExpressionParser.Alternation; +import io.cucumber.cucumberexpressions.CucumberExpressionParser.Node; +import io.cucumber.cucumberexpressions.CucumberExpressionParser.Optional; +import io.cucumber.cucumberexpressions.CucumberExpressionParser.Parameter; +import io.cucumber.cucumberexpressions.CucumberExpressionParser.Text; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; + +class CucumberExpressionParserTest { + + private CucumberExpressionParser parser = new CucumberExpressionParser(); + + @Test + void emptyString() { + assertThat(parser.parse(""), empty()); + } + + @Test + void phrase() { + assertThat(parser.parse("three blind mice"), contains( + node("three", Text.class), + node(" ", Text.class), + node("blind", Text.class), + node(" ", Text.class), + node("mice", Text.class) + )); + } + + @Test + void optional() { + assertThat(parser.parse("(blind)"), contains( + node("blind", Optional.class) + )); + } + + @Test + void parameter() { + assertThat(parser.parse("{string}"), contains( + node("string", Parameter.class) + )); + } + + @Test + void anonymousParameter() { + assertThat(parser.parse("{}"), contains( + node("", Parameter.class) + )); + } + + @Test + void optionalPhrase() { + assertThat(parser.parse("three (blind) mice"), contains( + node("three", Text.class), + node(" ", Text.class), + node("blind", Optional.class), + node(" ", Text.class), + node("mice", Text.class) + )); + } + + @Test + void slash() { + assertThat(parser.parse("\\"), contains( + node("\\", Text.class) + )); + } + + @Test + void openingParenthesis() { + assertThat(parser.parse("("), contains( + node("(", Text.class) + )); + } + + @Test + void escapedOpeningParenthesis() { + assertThat(parser.parse("\\("), contains( + node("\\(", Text.class) + )); + } + + @Test + void escapedOptional() { + assertThat(parser.parse("\\(blind)"), contains( + node("\\(", Text.class), + node("blind", Text.class), + node(")", Text.class) + )); + } + + @Test + void escapedOptionalPhrase() { + assertThat(parser.parse("three \\(blind) mice"), contains( + node("three", Text.class), + node(" ", Text.class), + node("\\(", Text.class), + node("blind", Text.class), + node(")", Text.class), + node(" ", Text.class), + node("mice", Text.class) + )); + } + + @Test + void escapedOptionalFollowedByOptional() { + assertThat(parser.parse("three \\((very) blind) mice"), contains( + node("three", Text.class), + node(" ", Text.class), + node("\\(", Text.class), + node("very", Optional.class), + node(" ", Text.class), + node("blind", Text.class), + node(")", Text.class), + node(" ", Text.class), + node("mice", Text.class) + )); + } + + @Test + void optionalContainingEscapedOptional() { + assertThat(parser.parse("three (\\(very\\) blind) mice"), contains( + node("three", Text.class), + node(" ", Text.class), + node("(very) blind", Optional.class), + node(" ", Text.class), + node("mice", Text.class) + )); + } + + + @Test + void alternation() { + assertThat(parser.parse("mice/rats"), contains( + node("mice - rats", Alternation.class) + )); + } + + @Test + void escapedAlternation() { + assertThat(parser.parse("mice\\/rats"), contains( + node("mice", Text.class), + node("/", Text.class), + node("rats", Text.class) + )); + } + + + @Test + void alternationPhrase() { + assertThat(parser.parse("three hungry/blind mice"), contains( + node("three", Text.class), + node(" ", Text.class), + node("hungry - blind", Alternation.class), + node(" ", Text.class), + node("mice", Text.class) + )); + } + + @Test + void alternationWithWhiteSpace() { + assertThat(parser.parse("\\ three\\ hungry/blind\\ mice\\ "), contains( + node(" three hungry - blind mice ", Alternation.class) + )); + } + + private static Matcher node(String expectedExpression, Class type) { + return new TypeSafeDiagnosingMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("("); + description.appendValue(expectedExpression); + description.appendText(","); + description.appendValue(type.getSimpleName()); + description.appendText(")"); + } + + @Override + protected boolean matchesSafely(Node node, Description description) { + description.appendText("("); + String expression = node.toString(); + description.appendValue(expression); + description.appendText(","); + description.appendValue(node.getClass().getSimpleName()); + description.appendText(")"); + return expectedExpression.equals(expression) && type.equals(node.getClass()); + } + }; + } + +} diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java new file mode 100644 index 0000000000..75e0af5a14 --- /dev/null +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -0,0 +1,142 @@ +package io.cucumber.cucumberexpressions; + + +import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; +import org.junit.jupiter.api.Test; + +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_ALTERNATION; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_END_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_END_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_WHITE_SPACE; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.TEXT; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; + +class CucumberExpressionTokenizerTest { + + private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + + + @Test + void emptyString() { + assertThat(tokenizer.tokenize(""), empty()); + } + + @Test + void phrase() { + assertThat(tokenizer.tokenize("three blind mice"), contains( + new Token("three", TEXT), + new Token(" ", WHITE_SPACE), + new Token("blind", TEXT), + new Token(" ", WHITE_SPACE), + new Token("mice", TEXT) + )); + } + + @Test + void optional() { + assertThat(tokenizer.tokenize("(blind)"), contains( + new Token("(", BEGIN_OPTIONAL), + new Token("blind", TEXT), + new Token(")", END_OPTIONAL) + )); + } + + @Test + void escapedOptional() { + assertThat(tokenizer.tokenize("\\(blind\\)"), contains( + new Token("\\(", ESCAPED_BEGIN_OPTIONAL), + new Token("blind", TEXT), + new Token("\\)", ESCAPED_END_OPTIONAL) + )); + } + + @Test + void optionalPhrase() { + assertThat(tokenizer.tokenize("three (blind) mice"), contains( + new Token("three", TEXT), + new Token(" ", WHITE_SPACE), + new Token("(", BEGIN_OPTIONAL), + new Token("blind", TEXT), + new Token(")", END_OPTIONAL), + new Token(" ", WHITE_SPACE), + new Token("mice", TEXT) + )); + } + + @Test + void parameter() { + assertThat(tokenizer.tokenize("{string}"), contains( + new Token("{", BEGIN_PARAMETER), + new Token("string", TEXT), + new Token("}", END_PARAMETER) + )); + } + + @Test + void EscapedParameter() { + assertThat(tokenizer.tokenize("\\{string\\}"), contains( + new Token("\\{", ESCAPED_BEGIN_PARAMETER), + new Token("string", TEXT), + new Token("\\}", ESCAPED_END_PARAMETER) + )); + } + + @Test + void parameterPhrase() { + assertThat(tokenizer.tokenize("three {string} mice"), contains( + new Token("three", TEXT), + new Token(" ", WHITE_SPACE), + new Token("{", BEGIN_PARAMETER), + new Token("string", TEXT), + new Token("}", END_PARAMETER), + new Token(" ", WHITE_SPACE), + new Token("mice", TEXT) + )); + } + + + @Test + void alternation() { + assertThat(tokenizer.tokenize("(blind)"), contains( + new Token("(", BEGIN_OPTIONAL), + new Token("blind", TEXT), + new Token(")", END_OPTIONAL) + )); + } + + @Test + void escapedAlternation() { + assertThat(tokenizer.tokenize("blind\\ and\\ famished\\/cripple"), contains( + new Token("blind", TEXT), + new Token("\\ ", ESCAPED_WHITE_SPACE), + new Token("and", TEXT), + new Token("\\ ", ESCAPED_WHITE_SPACE), + new Token("famished", TEXT), + new Token("\\/", ESCAPED_ALTERNATION), + new Token("cripple", TEXT) + )); + } + + @Test + void alternationPhrase() { + assertThat(tokenizer.tokenize("three blind/cripple mice"), contains( + new Token("three", TEXT), + new Token(" ", WHITE_SPACE), + new Token("blind", TEXT), + new Token("/", ALTERNATION), + new Token("cripple", TEXT), + new Token(" ", WHITE_SPACE), + new Token("mice", TEXT) + )); + } +} From a09de33c93b6cce8eb0b16a0075b985469f502e4 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 31 Oct 2019 18:38:50 +0100 Subject: [PATCH 026/183] Rewrite AST to Regex --- .../CucumberExpression.java | 201 +++++++----------- .../CucumberExpressionParser.java | 70 ++++-- .../ExpressionFactory.java | 4 +- .../CucumberExpressionParserTest.java | 8 +- .../CucumberExpressionTest.java | 12 +- 5 files changed, 147 insertions(+), 148 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 9ad2853e0b..553221a8c7 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -4,25 +4,18 @@ import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.function.Function; -import java.util.regex.MatchResult; -import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Stream; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; import static java.util.stream.Collectors.joining; @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { private static final Pattern ESCAPE_PATTERN = Pattern.compile("([\\\\^\\[({$.|?*+})\\]])"); - @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces - static final Pattern PARAMETER_PATTERN = Pattern.compile("((?:\\\\){0,2})\\{([^}]*)\\}"); - private static final Pattern OPTIONAL_PATTERN = Pattern.compile("((?:\\\\){0,2})\\(([^)]+)\\)"); - private static final Pattern ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = Pattern.compile("([^\\s/]*)((/[^\\s/]*)+)"); - private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; + private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String ALTERNATIVE_MAY_NOT_BE_EMPTY = "Alternative may not be empty: "; private final List> parameterTypes = new ArrayList<>(); @@ -30,115 +23,106 @@ public final class CucumberExpression implements Expression { private final TreeRegexp treeRegexp; private final ParameterTypeRegistry parameterTypeRegistry; - private static class Token { - final String text; - final Type type; - - private Token(String text, Type type) { - this.text = text; - this.type = type; - } - - private enum Type { - TEXT, OPTIONAL, ALTERNATION, PARAMETER - } - } - CucumberExpression(String expression, ParameterTypeRegistry parameterTypeRegistry) { this.source = expression; this.parameterTypeRegistry = parameterTypeRegistry; - String pattern = Stream.of(new Token(expression, Token.Type.TEXT)) - .flatMap(processOptional()) - .flatMap(processAlternation()) - .flatMap(processParameters()) - .map(escapeRegex()) + CucumberExpressionParser parser = new CucumberExpressionParser(); + List parse = parser.parse(expression); + + String pattern = parse.stream() + .map(this::validate) + .map(this::process) .collect(joining("", "^", "$")); - treeRegexp = new TreeRegexp(pattern); - } - private static Function escapeRegex() { - return token -> token.type != Token.Type.TEXT ? token.text : escapeRegex(token.text); + treeRegexp = new TreeRegexp(pattern); } - private static String escapeRegex(String text) { - return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); - } + private String process(CucumberExpressionParser.Node node) { + if (node instanceof CucumberExpressionParser.Optional) { + CucumberExpressionParser.Optional optional = (CucumberExpressionParser.Optional) node; + return "(?:" + escapeRegex(optional.getOptionalText()) + ")?"; + } - private Function> processAlternation() { - return splitTextTokens(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, (match) -> { - // replace \/ with / - // replace / with | - String replacement = match.group(0) - .replace('/', '|') - .replaceAll("\\\\\\|", "/"); - - if (!replacement.contains("|")) { - // All / were escaped - return new Token(replacement, Token.Type.TEXT); + if (node instanceof CucumberExpressionParser.Alternation) { + CucumberExpressionParser.Alternation alternation = (CucumberExpressionParser.Alternation) node; + return alternation.getAlternatives() + .stream() + .map(CucumberExpression::escapeRegex) + .collect(joining("|","(?:",")")); + } + if (node instanceof CucumberExpressionParser.Parameter) { + CucumberExpressionParser.Parameter parameter = (CucumberExpressionParser.Parameter) node; + String typeName = parameter.getParameterName(); + ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); + if (parameterType == null) { + throw new UndefinedParameterTypeException(typeName); } + parameterTypes.add(parameterType); + return buildCaptureRegexp(parameterType.getRegexps()); + } - // Make sure the alternative parts aren't empty and don't contain parameter types - String[] alternatives = replacement.split("\\|"); - if (alternatives.length == 0) { - throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); - } - for (String alternative : alternatives) { - if (alternative.isEmpty()) { - throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); - } - checkNotParameterType(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); - } - String pattern = Arrays.stream(alternatives) - .map(CucumberExpression::escapeRegex) - .collect(joining("|", "(?:", ")")); + if (node instanceof CucumberExpressionParser.Text) { + CucumberExpressionParser.Text text = (CucumberExpressionParser.Text) node; + return escapeRegex(text.getText()); + } - return new Token(pattern, Token.Type.ALTERNATION); - }); + throw new IllegalArgumentException(node.getClass().getName()); } - private void checkNotParameterType(String s, String message) { - Matcher matcher = PARAMETER_PATTERN.matcher(s); - if (matcher.find()) { - throw new CucumberExpressionException(message + source); + private CucumberExpressionParser.Node validate(CucumberExpressionParser.Node node) { + if (node instanceof CucumberExpressionParser.Optional) { + CucumberExpressionParser.Optional optional = (CucumberExpressionParser.Optional) node; + return validateOptional(optional); + } + + if (node instanceof CucumberExpressionParser.Alternation) { + CucumberExpressionParser.Alternation alternation = (CucumberExpressionParser.Alternation) node; + return validateAlternation(alternation); + } + if (node instanceof CucumberExpressionParser.Parameter) { + CucumberExpressionParser.Parameter parameter = (CucumberExpressionParser.Parameter) node; + return validateParameter(parameter); } + + return node; + } + + private CucumberExpressionParser.Node validateParameter(CucumberExpressionParser.Parameter parameter) { + ParameterType.checkParameterTypeName(parameter.getParameterName()); + return parameter; } - private Function> processOptional() { - return splitTextTokens(OPTIONAL_PATTERN, (match) -> { - String parameterPart = match.group(2); - String escapes = match.group(1); - // look for single-escaped parentheses - if (escapes.length() == 1) { - return new Token("(" + parameterPart + ")", Token.Type.TEXT); + private CucumberExpressionParser.Node validateAlternation(CucumberExpressionParser.Alternation alternation) { + // Make sure the alternative parts aren't empty and don't contain parameter types + checkNotParameterType(alternation.tokens, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); + if (alternation.alternatives.isEmpty()) { + throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); + } + + for (List alternative : alternation.alternatives) { + if (alternative.isEmpty()) { + throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } + } + return alternation; + } - checkNotParameterType(parameterPart, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - String pattern = "(?:" + escapeRegex(parameterPart) + ")?"; - // either no or double escape - return new Token(escapes + pattern, Token.Type.OPTIONAL); - }); + private CucumberExpressionParser.Node validateOptional(CucumberExpressionParser.Optional optional) { + checkNotParameterType(optional.tokens, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); + return optional; } - private Function> processParameters() { - return splitTextTokens(PARAMETER_PATTERN, (match) -> { - String typeName = match.group(2); - String escapes = match.group(1); - // look for single-escaped parentheses - if (escapes.length() == 1) { - return new Token("{" + typeName + "}", Token.Type.TEXT); - } - ParameterType.checkParameterTypeName(typeName); - ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); - if (parameterType == null) { - throw new UndefinedParameterTypeException(typeName); - } - parameterTypes.add(parameterType); - String pattern = buildCaptureRegexp(parameterType.getRegexps()); - // either no or double escape - return new Token(escapes + pattern, Token.Type.PARAMETER); - }); + private static String escapeRegex(String text) { + return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); + } + + private void checkNotParameterType(List tokens, String message) { + if (tokens.stream().anyMatch(token -> token.type == BEGIN_PARAMETER) + && tokens.stream().anyMatch(token -> token.type == END_PARAMETER)) { + throw new CucumberExpressionException(message + source); + } } private String buildCaptureRegexp(List regexps) { @@ -149,29 +133,6 @@ private String buildCaptureRegexp(List regexps) { .collect(joining(")|(?:", "((?:", "))")); } - private static Function> splitTextTokens(Pattern pattern, Function processor) { - return token -> { - if (token.type != Token.Type.TEXT) { - return Stream.of(token); - } - String expression = token.text; - List tokens = new ArrayList<>(); - Matcher matcher = pattern.matcher(token.text); - int previousEnd = 0; - while (matcher.find()) { - int start = matcher.start(); - int end = matcher.end(); - String prefix = expression.substring(previousEnd, start); - tokens.add(new Token(prefix, Token.Type.TEXT)); - tokens.add(processor.apply(matcher.toMatchResult())); - previousEnd = end; - } - String suffix = expression.substring(previousEnd); - tokens.add(new Token(suffix, Token.Type.TEXT)); - return tokens.stream(); - }; - } - @Override public List> match(String text, Type... typeHints) { final Group group = treeRegexp.match(text); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 35a0c76c1f..f825c2dedb 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import java.util.stream.Collectors; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL; @@ -46,21 +47,18 @@ private static Parse parseAlternation() { return 0; } - boolean contiguous = expression - .subList(current, pivot) - .stream() - .noneMatch(t -> t.type == WHITE_SPACE); - if (!contiguous) { + int rightHandBoundary = findFirst(expression, current, WHITE_SPACE, END_OPTIONAL); + if (rightHandBoundary >= 0 && rightHandBoundary < pivot) { return 0; } - int endToken = findFirst(expression, pivot, WHITE_SPACE); - if (endToken < 0) { - endToken = expression.size(); + int leftHandBoundary = findFirst(expression, pivot, WHITE_SPACE, BEGIN_OPTIONAL); + if (leftHandBoundary < 0) { + leftHandBoundary = expression.size(); } - ast.add(new Alternation(expression.subList(current, endToken))); - return endToken - current; + ast.add(new Alternation(expression.subList(current, leftHandBoundary))); + return leftHandBoundary - current; }; } @@ -81,11 +79,13 @@ private static Parse parseBetween(Function, Node> createNode, Token. }; } - private static int findFirst(List expression, int fromIndex, Token.Type end) { + private static int findFirst(List expression, int fromIndex, Token.Type... end) { for (int i = fromIndex; i < expression.size(); i++) { Token candidate = expression.get(i); - if (candidate.type == end) { - return i; + for (Token.Type type : end) { + if (candidate.type == type) { + return i; + } } } return -1; @@ -116,10 +116,24 @@ static final class Text extends Node { @Override public String toString() { + return getText(); + } + + String getText() { return tokens.stream().map(token -> { switch (token.type) { case ESCAPED_ALTERNATION: return "/"; + case ESCAPED_BEGIN_OPTIONAL: + return "("; + case ESCAPED_END_OPTIONAL: + return ")"; + case ESCAPED_BEGIN_PARAMETER: + return "{"; + case ESCAPED_END_PARAMETER: + return "}"; + case ESCAPED_ESCAPE: + return "\\"; default: return token.text; } @@ -135,6 +149,10 @@ static final class Optional extends Node { @Override public String toString() { + return getOptionalText(); + } + + String getOptionalText() { return tokens.stream() .map(token -> { switch (token.type) { @@ -158,6 +176,10 @@ static final class Parameter extends Node { @Override public String toString() { + return getParameterName(); + } + + String getParameterName() { return tokens.stream() .map(token -> { switch (token.type) { @@ -176,7 +198,7 @@ public String toString() { static final class Alternation extends Node { - private final ArrayList> alternatives; + final List> alternatives; Alternation(List tokens) { super(tokens); @@ -197,8 +219,7 @@ static final class Alternation extends Node { } } - @Override - public String toString() { + public List getAlternatives() { return alternatives.stream() .map(alternatives -> alternatives .stream() @@ -208,17 +229,28 @@ public String toString() { return " "; case ESCAPED_ALTERNATION: return "/"; + case ESCAPED_ESCAPE: + return "\\"; + case ESCAPED_BEGIN_OPTIONAL: + return "("; + case ESCAPED_BEGIN_PARAMETER: + return "{"; default: return token.text; } }).collect(joining())) - .collect(joining(" - ")); + .collect(Collectors.toList()); + } + + @Override + public String toString() { + return String.join(" - ", getAlternatives()); } } - List parse(String expressionStr) { - List tokens = tokenizer.tokenize(expressionStr); + List parse(String expression) { + List tokens = tokenizer.tokenize(expression); List ast = new ArrayList<>(); int length = tokens.size(); int current = 0; diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java index cb5049500a..5d93c4464e 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java @@ -17,6 +17,8 @@ public final class ExpressionFactory { private static final Pattern BEGIN_ANCHOR = Pattern.compile("^\\^.*"); private static final Pattern END_ANCHOR = Pattern.compile(".*\\$$"); private static final Pattern SCRIPT_STYLE_REGEXP = Pattern.compile("^/(.*)/$"); + private static final Pattern PARAMETER_PATTERN = Pattern.compile("((?:\\\\){0,2})\\{([^}]*)\\}"); + private final ParameterTypeRegistry parameterTypeRegistry; public ExpressionFactory(ParameterTypeRegistry parameterTypeRegistry) { @@ -38,7 +40,7 @@ private RegularExpression createRegularExpressionWithAnchors(String expressionSt try { return new RegularExpression(Pattern.compile(expressionString), parameterTypeRegistry); } catch (PatternSyntaxException e) { - if (CucumberExpression.PARAMETER_PATTERN.matcher(expressionString).find()) { + if (PARAMETER_PATTERN.matcher(expressionString).find()) { throw new CucumberExpressionException("You cannot use anchors (^ or $) in Cucumber Expressions. Please remove them from " + expressionString, e); } throw e; diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 758d334d9f..57b84564fd 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -83,14 +83,14 @@ void openingParenthesis() { @Test void escapedOpeningParenthesis() { assertThat(parser.parse("\\("), contains( - node("\\(", Text.class) + node("(", Text.class) )); } @Test void escapedOptional() { assertThat(parser.parse("\\(blind)"), contains( - node("\\(", Text.class), + node("(", Text.class), node("blind", Text.class), node(")", Text.class) )); @@ -101,7 +101,7 @@ void escapedOptionalPhrase() { assertThat(parser.parse("three \\(blind) mice"), contains( node("three", Text.class), node(" ", Text.class), - node("\\(", Text.class), + node("(", Text.class), node("blind", Text.class), node(")", Text.class), node(" ", Text.class), @@ -114,7 +114,7 @@ void escapedOptionalFollowedByOptional() { assertThat(parser.parse("three \\((very) blind) mice"), contains( node("three", Text.class), node(" ", Text.class), - node("\\(", Text.class), + node("(", Text.class), node("very", Optional.class), node(" ", Text.class), node("blind", Text.class), diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 05d58d87ca..5a8af6a26f 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -21,11 +21,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class CucumberExpressionTest { + private final ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); @Test public void documents_match_arguments() { - ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); - String expr = "I have {int} cuke(s)"; Expression expression = new CucumberExpression(expr, parameterTypeRegistry); List> args = expression.match("I have 7 cukes"); @@ -138,6 +137,10 @@ public void matches_double_quoted_empty_string_as_empty_string_along_with_other_ @Test public void matches_escaped_parenthesis() { assertEquals(emptyList(), match("three \\(exceptionally) \\{string} mice", "three (exceptionally) {string} mice")); + assertEquals(singletonList("blind"), match("three \\((exceptionally)\\) \\{{string}\\} mice", "three (exceptionally) {\"blind\"} mice")); + assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); + parameterTypeRegistry.defineParameterType(new ParameterType<>("{string}", "\"(.*)\"", String.class, (String arg) -> arg)); + assertEquals(singletonList("blind"), match("three (\\(exceptionally\\)) {\\{string\\}} mice", "three (exceptionally) \"blind\" mice")); } @Test @@ -152,7 +155,8 @@ public void matches_escaped_slash() { @Test public void matches_doubly_escaped_slash() { - assertEquals(emptyList(), match("12\\\\/2020", "12\\/2020")); + assertEquals(emptyList(), match("12\\\\/2020", "12\\")); + assertEquals(emptyList(), match("12\\\\/2020", "2020")); } @Test @@ -301,7 +305,7 @@ public List transform(String... args) { } private List match(String expr, String text, Type... typeHints) { - return match(expr, text, Locale.ENGLISH, typeHints); + return match(expr, text, parameterTypeRegistry, typeHints); } private List match(String expr, String text, Locale locale, Type... typeHints) { From 69ef46810c77f1c6f2ba5e5c290d9cbdcdc20a7f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 31 Oct 2019 18:52:21 +0100 Subject: [PATCH 027/183] Fix --- .../cucumberexpressions/CucumberExpressionParser.java | 4 ---- .../cucumber/cucumberexpressions/CucumberExpressionTest.java | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index f825c2dedb..2e49579242 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -126,12 +126,8 @@ String getText() { return "/"; case ESCAPED_BEGIN_OPTIONAL: return "("; - case ESCAPED_END_OPTIONAL: - return ")"; case ESCAPED_BEGIN_PARAMETER: return "{"; - case ESCAPED_END_PARAMETER: - return "}"; case ESCAPED_ESCAPE: return "\\"; default: diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 5a8af6a26f..8310248b53 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -137,7 +137,7 @@ public void matches_double_quoted_empty_string_as_empty_string_along_with_other_ @Test public void matches_escaped_parenthesis() { assertEquals(emptyList(), match("three \\(exceptionally) \\{string} mice", "three (exceptionally) {string} mice")); - assertEquals(singletonList("blind"), match("three \\((exceptionally)\\) \\{{string}\\} mice", "three (exceptionally) {\"blind\"} mice")); + assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); parameterTypeRegistry.defineParameterType(new ParameterType<>("{string}", "\"(.*)\"", String.class, (String arg) -> arg)); assertEquals(singletonList("blind"), match("three (\\(exceptionally\\)) {\\{string\\}} mice", "three (exceptionally) \"blind\" mice")); From 006cfbebbd91857a5b19f7e001a9b25f51a98c81 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 31 Oct 2019 19:31:10 +0100 Subject: [PATCH 028/183] Fix --- .../CucumberExpression.java | 37 +++---------------- .../CucumberExpressionParser.java | 6 ++- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 553221a8c7..48614174c9 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -31,7 +31,6 @@ public final class CucumberExpression implements Expression { List parse = parser.parse(expression); String pattern = parse.stream() - .map(this::validate) .map(this::process) .collect(joining("", "^", "$")); @@ -42,11 +41,13 @@ public final class CucumberExpression implements Expression { private String process(CucumberExpressionParser.Node node) { if (node instanceof CucumberExpressionParser.Optional) { CucumberExpressionParser.Optional optional = (CucumberExpressionParser.Optional) node; + checkNotParameterType(optional.tokens, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); return "(?:" + escapeRegex(optional.getOptionalText()) + ")?"; } if (node instanceof CucumberExpressionParser.Alternation) { CucumberExpressionParser.Alternation alternation = (CucumberExpressionParser.Alternation) node; + validateAlternation(alternation); return alternation.getAlternatives() .stream() .map(CucumberExpression::escapeRegex) @@ -54,6 +55,7 @@ private String process(CucumberExpressionParser.Node node) { } if (node instanceof CucumberExpressionParser.Parameter) { CucumberExpressionParser.Parameter parameter = (CucumberExpressionParser.Parameter) node; + ParameterType.checkParameterTypeName(parameter.getParameterName()); String typeName = parameter.getParameterName(); ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); if (parameterType == null) { @@ -71,32 +73,8 @@ private String process(CucumberExpressionParser.Node node) { throw new IllegalArgumentException(node.getClass().getName()); } - private CucumberExpressionParser.Node validate(CucumberExpressionParser.Node node) { - if (node instanceof CucumberExpressionParser.Optional) { - CucumberExpressionParser.Optional optional = (CucumberExpressionParser.Optional) node; - return validateOptional(optional); - } - - if (node instanceof CucumberExpressionParser.Alternation) { - CucumberExpressionParser.Alternation alternation = (CucumberExpressionParser.Alternation) node; - return validateAlternation(alternation); - } - if (node instanceof CucumberExpressionParser.Parameter) { - CucumberExpressionParser.Parameter parameter = (CucumberExpressionParser.Parameter) node; - return validateParameter(parameter); - } - - return node; - } - - private CucumberExpressionParser.Node validateParameter(CucumberExpressionParser.Parameter parameter) { - ParameterType.checkParameterTypeName(parameter.getParameterName()); - return parameter; - } - - private CucumberExpressionParser.Node validateAlternation(CucumberExpressionParser.Alternation alternation) { + private void validateAlternation(CucumberExpressionParser.Alternation alternation) { // Make sure the alternative parts aren't empty and don't contain parameter types - checkNotParameterType(alternation.tokens, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); if (alternation.alternatives.isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } @@ -105,13 +83,8 @@ private CucumberExpressionParser.Node validateAlternation(CucumberExpressionPars if (alternative.isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } + checkNotParameterType(alternation.tokens, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); } - return alternation; - } - - private CucumberExpressionParser.Node validateOptional(CucumberExpressionParser.Optional optional) { - checkNotParameterType(optional.tokens, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - return optional; } private static String escapeRegex(String text) { diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 2e49579242..96658a879d 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -215,7 +215,7 @@ static final class Alternation extends Node { } } - public List getAlternatives() { + List getAlternatives() { return alternatives.stream() .map(alternatives -> alternatives .stream() @@ -229,8 +229,12 @@ public List getAlternatives() { return "\\"; case ESCAPED_BEGIN_OPTIONAL: return "("; + case ESCAPED_END_OPTIONAL: + return ")"; case ESCAPED_BEGIN_PARAMETER: return "{"; + case ESCAPED_END_PARAMETER: + return "}"; default: return token.text; } From b01171a817581dd3af7235b4e7fdfa989630aba7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 31 Oct 2019 19:32:09 +0100 Subject: [PATCH 029/183] Fix --- .../CucumberExpressionParser.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 96658a879d..880afe7c56 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -47,18 +47,18 @@ private static Parse parseAlternation() { return 0; } - int rightHandBoundary = findFirst(expression, current, WHITE_SPACE, END_OPTIONAL); - if (rightHandBoundary >= 0 && rightHandBoundary < pivot) { + int leftHandBoundary = findFirst(expression, current, WHITE_SPACE, END_OPTIONAL); + if (leftHandBoundary >= 0 && leftHandBoundary < pivot) { return 0; } - int leftHandBoundary = findFirst(expression, pivot, WHITE_SPACE, BEGIN_OPTIONAL); - if (leftHandBoundary < 0) { - leftHandBoundary = expression.size(); + int rightHandBoundary = findFirst(expression, pivot, WHITE_SPACE, BEGIN_OPTIONAL); + if (rightHandBoundary < 0) { + rightHandBoundary = expression.size(); } - ast.add(new Alternation(expression.subList(current, leftHandBoundary))); - return leftHandBoundary - current; + ast.add(new Alternation(expression.subList(current, rightHandBoundary))); + return rightHandBoundary - current; }; } From ec8c89e06b86756eee24249dfecba1e030c29faa Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 31 Oct 2019 19:50:47 +0100 Subject: [PATCH 030/183] Fix --- cucumber-expressions/README.md | 6 +++--- .../cucumberexpressions/CucumberExpression.java | 17 +++++++++++++++-- .../CucumberExpressionParser.java | 10 +++++----- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 3400ab1406..a91994879c 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -5,14 +5,14 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ``` cucumber-expression := [ optional | escaped-optional | other-then-optional ]* -optional := `\\`? + '(' + text + ')' +optional := '(' + text + ')' escaped-optional := '\(' + other-then-optional + ')' other-then-optional: = [ alternative | escaped-alternative | other-then-alternative ]* -alternative := text + [ `\\`? + '/' + text ]+ +alternative := text + [ '/' + text ]+ escaped-alternative := other-then-alternative +[ '\/' + other-then-alternative ]+ other-then-alternative := [ parameter | escaped-parameter | other-then-parameter ]* parameter := '{' + text + '}' -escaped-parameter := `\\`? + '\{' + other-then-parameter + '}' +escaped-parameter := '\{' + other-then-parameter + '}' other-then-parameter:= text text := .* ``` diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 48614174c9..6b6ce3d403 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -92,8 +92,21 @@ private static String escapeRegex(String text) { } private void checkNotParameterType(List tokens, String message) { - if (tokens.stream().anyMatch(token -> token.type == BEGIN_PARAMETER) - && tokens.stream().anyMatch(token -> token.type == END_PARAMETER)) { + int beginParameterIndex = -1; + int endParameterIndex = -1; + for (int i = 0; i < tokens.size(); i++) { + CucumberExpressionTokenizer.Token token = tokens.get(i); + if (beginParameterIndex == -1 && token.type == BEGIN_PARAMETER) { + beginParameterIndex = i; + } + if (endParameterIndex == -1 && token.type == END_PARAMETER) { + endParameterIndex = i; + } + } + if(beginParameterIndex == -1 || endParameterIndex == -1){ + return; + } + if (beginParameterIndex < endParameterIndex) { throw new CucumberExpressionException(message + source); } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 880afe7c56..9e25651528 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -52,13 +52,13 @@ private static Parse parseAlternation() { return 0; } - int rightHandBoundary = findFirst(expression, pivot, WHITE_SPACE, BEGIN_OPTIONAL); - if (rightHandBoundary < 0) { - rightHandBoundary = expression.size(); + int rightHandBoundry = findFirst(expression, pivot, WHITE_SPACE, BEGIN_OPTIONAL); + if (rightHandBoundry < 0) { + rightHandBoundry = expression.size(); } - ast.add(new Alternation(expression.subList(current, rightHandBoundary))); - return rightHandBoundary - current; + ast.add(new Alternation(expression.subList(current, rightHandBoundry))); + return rightHandBoundry - current; }; } From 7d9463a3499ff3cc56250be42e561a472374696a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 09:47:38 +0100 Subject: [PATCH 031/183] Extract token rewrite rules --- .../CucumberExpression.java | 2 +- .../CucumberExpressionParser.java | 284 ++++++++++-------- .../CucumberExpressionTokenizer.java | 13 +- 3 files changed, 161 insertions(+), 138 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 6b6ce3d403..4d13131ad0 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -83,7 +83,7 @@ private void validateAlternation(CucumberExpressionParser.Alternation alternatio if (alternative.isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } - checkNotParameterType(alternation.tokens, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); + checkNotParameterType(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 9e25651528..c902b0c6a5 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -3,7 +3,9 @@ import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; import java.util.ArrayList; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; @@ -12,36 +14,119 @@ import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPE; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_ALTERNATION; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_END_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_END_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_ESCAPE; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_WHITE_SPACE; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; final class CucumberExpressionParser { - private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + private static final Token BEGIN_PARAMETER_TOKEN = new Token("{", BEGIN_PARAMETER); + private static final Token END_PARAMETER_TOKEN = new Token("}", END_PARAMETER); + private static final Token BEGIN_OPTIONAL_TOKEN = new Token("(", BEGIN_OPTIONAL); + private static final Token END_OPTIONAL_TOKEN = new Token(")", END_OPTIONAL); + private static final Token ESCAPE_TOKEN = new Token("\\", ESCAPE); + private static final Token ALTERNATION_TOKEN = new Token("/", ALTERNATION); + + private static final Function escapeOptional; + private static final Function escapeAlternation; + private static final Function escapeParameter; + private static final Function escapeText; + + + static { + Map> escapesInOptional = new EnumMap<>(Token.Type.class); + escapesInOptional.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); + escapesInOptional.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); + escapesInOptional.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); + escapeOptional = rewrite(escapesInOptional); + + Map> escapesInAlternation = new EnumMap<>(Token.Type.class); + escapesInAlternation.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); + escapesInAlternation.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); + escapesInAlternation.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); + escapesInAlternation.put(ESCAPED_WHITE_SPACE, token -> new Token(token.text.substring(1), WHITE_SPACE)); + escapesInAlternation.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); + // Rewrite the token text but retain the type. This allows Cucumber Expressions + // to validate there are no parameters in alternation but renders escapes correctly. + escapesInAlternation.put(ESCAPED_BEGIN_PARAMETER, token -> new Token("{", ESCAPED_BEGIN_PARAMETER)); + escapesInAlternation.put(ESCAPED_END_PARAMETER, token -> new Token("}", ESCAPED_END_PARAMETER)); + escapeAlternation = rewrite(escapesInAlternation); + + Map> escapesInParameter = new EnumMap<>(Token.Type.class); + escapesInParameter.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); + escapesInAlternation.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); + escapesInAlternation.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); + escapesInAlternation.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); + escapesInParameter.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); + escapesInParameter.put(ESCAPED_END_PARAMETER, token -> END_PARAMETER_TOKEN); + escapeParameter = rewrite(escapesInParameter); + + Map> escapesInText = new EnumMap<>(Token.Type.class); + escapesInText.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); + escapesInText.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); + escapesInText.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); + escapesInText.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); + escapeText = rewrite(escapesInText); + } + + private interface Parse { + int parse(List ast, List expression, int current); + } private static final List parsers = asList( - parseBetween(Optional::new, BEGIN_OPTIONAL, END_OPTIONAL), + parseBetween(BEGIN_OPTIONAL, END_OPTIONAL, Optional::new, escapeOptional), parseAlternation(), - parseBetween(Parameter::new, BEGIN_PARAMETER, END_PARAMETER), + parseBetween(BEGIN_PARAMETER, END_PARAMETER, Parameter::new, escapeParameter), parseText() ); + private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + + List parse(String expression) { + List tokens = tokenizer.tokenize(expression); + List ast = new ArrayList<>(); + int length = tokens.size(); + int current = 0; + while (current < length) { + boolean parsed = false; + for (Parse parser : parsers) { + int consumedChars = parser.parse(ast, tokens, current); + if (consumedChars != 0) { + current += consumedChars; + parsed = true; + break; + } + } + if (!parsed) { + // Can't happen if configured properly + // Leave in to avoid looping if not configured properly + throw new IllegalStateException("Could not parse " + tokens); + } + } + return ast; + } + private static Parse parseText() { return (ast, expression, current) -> { - ast.add(new Text(singletonList(expression.get(current)))); + Token token = expression.get(current); + Token unescaped = escapeText.apply(token); + ast.add(new Text(singletonList(unescaped))); return 1; }; } private static Parse parseAlternation() { return (ast, expression, current) -> { - Token token = expression.get(current); - if (token.type == WHITE_SPACE) { - return 0; - } - int pivot = findFirst(expression, current, ALTERNATION); if (pivot < 0) { return 0; @@ -52,17 +137,29 @@ private static Parse parseAlternation() { return 0; } - int rightHandBoundry = findFirst(expression, pivot, WHITE_SPACE, BEGIN_OPTIONAL); - if (rightHandBoundry < 0) { - rightHandBoundry = expression.size(); + int rightHandBoundary = findFirst(expression, pivot, WHITE_SPACE, BEGIN_OPTIONAL); + if (rightHandBoundary < 0) { + rightHandBoundary = expression.size(); } - ast.add(new Alternation(expression.subList(current, rightHandBoundry))); - return rightHandBoundry - current; + List tokens = expression.subList(current, rightHandBoundary); + List> alternatives = split(tokens, ALTERNATION); + List> unescapedAlternative = alternatives.stream() + .map(alternative -> alternative.stream() + .map(escapeAlternation) + .collect(toList()) + ).collect(toList()); + ast.add(new Alternation(unescapedAlternative)); + // Does not consume right hand boundary token + return rightHandBoundary - current; }; } - private static Parse parseBetween(Function, Node> createNode, Token.Type startToken, Token.Type endToken) { + private static Parse parseBetween(Token.Type startToken, + Token.Type endToken, + Function, Node> createNode, + Function rewriteEscapes + ) { return (ast, expression, current) -> { Token token = expression.get(current); if (token.type != startToken) { @@ -70,15 +167,22 @@ private static Parse parseBetween(Function, Node> createNode, Token. } int endIndex = findFirst(expression, current + 1, endToken); - if (endIndex > 0) { - ast.add(createNode.apply(expression.subList(current + 1, endIndex))); - return endIndex - current + 1; + if (endIndex <= 0) { + return 0; } + List tokens = expression.subList(current + 1, endIndex); + List unescaped = tokens.stream().map(rewriteEscapes).collect(toList()); + ast.add(createNode.apply(unescaped)); + // Consumes end token + return endIndex + 1 - current; - return 0; }; } + private static Function rewrite(Map> rewriteRules) { + return token -> rewriteRules.computeIfAbsent(token.type, type -> Function.identity()).apply(token); + } + private static int findFirst(List expression, int fromIndex, Token.Type... end) { for (int i = fromIndex; i < expression.size(); i++) { Token candidate = expression.get(i); @@ -91,27 +195,32 @@ private static int findFirst(List expression, int fromIndex, Token.Type.. return -1; } - - private interface Parse { - - int parse(List ast, List expression, int current); - + private static List> split(List tokens, Token.Type type) { + List> alternatives = new ArrayList<>(); + List alternative = new ArrayList<>(); + alternatives.add(alternative); + for (Token token : tokens) { + if (token.type == type) { + alternative = new ArrayList<>(); + alternatives.add(alternative); + } else { + alternative.add(token); + } + } + return alternatives; } - static abstract class Node { - final List tokens; - Node(List tokens) { - this.tokens = tokens; - } + static abstract class Node { } - static final class Text extends Node { + final List tokens; + Text(List tokens) { - super(tokens); + this.tokens = tokens; } @Override @@ -120,27 +229,16 @@ public String toString() { } String getText() { - return tokens.stream().map(token -> { - switch (token.type) { - case ESCAPED_ALTERNATION: - return "/"; - case ESCAPED_BEGIN_OPTIONAL: - return "("; - case ESCAPED_BEGIN_PARAMETER: - return "{"; - case ESCAPED_ESCAPE: - return "\\"; - default: - return token.text; - } - }).collect(joining()); + return tokens.stream().map(token -> token.text).collect(joining()); } } static final class Optional extends Node { + final List tokens; + Optional(List tokens) { - super(tokens); + this.tokens = tokens; } @Override @@ -149,25 +247,16 @@ public String toString() { } String getOptionalText() { - return tokens.stream() - .map(token -> { - switch (token.type) { - case ESCAPED_BEGIN_OPTIONAL: - return "("; - case ESCAPED_END_OPTIONAL: - return ")"; - default: - return token.text; - } - }) - .collect(joining()); + return tokens.stream().map(token -> token.text).collect(joining()); } } static final class Parameter extends Node { + final List tokens; + Parameter(List tokens) { - super(tokens); + this.tokens = tokens; } @Override @@ -176,19 +265,7 @@ public String toString() { } String getParameterName() { - return tokens.stream() - .map(token -> { - switch (token.type) { - case ESCAPED_BEGIN_PARAMETER: - return "{"; - case ESCAPED_END_PARAMETER: - return "}"; - default: - return token.text; - } - - }) - .collect(joining()); + return tokens.stream().map(token -> token.text).collect(joining()); } } @@ -196,49 +273,16 @@ static final class Alternation extends Node { final List> alternatives; - Alternation(List tokens) { - super(tokens); - if (tokens.isEmpty()) { - throw new IllegalArgumentException("" + tokens.size()); - } - this.alternatives = new ArrayList<>(); - List alternative = new ArrayList<>(); - alternatives.add(alternative); - - for (Token token : tokens) { - if (token.type == ALTERNATION) { - alternative = new ArrayList<>(); - alternatives.add(alternative); - } else { - alternative.add(token); - } - } + Alternation(List> alternatives) { + this.alternatives = alternatives; } List getAlternatives() { return alternatives.stream() .map(alternatives -> alternatives .stream() - .map(token -> { - switch (token.type) { - case ESCAPED_WHITE_SPACE: - return " "; - case ESCAPED_ALTERNATION: - return "/"; - case ESCAPED_ESCAPE: - return "\\"; - case ESCAPED_BEGIN_OPTIONAL: - return "("; - case ESCAPED_END_OPTIONAL: - return ")"; - case ESCAPED_BEGIN_PARAMETER: - return "{"; - case ESCAPED_END_PARAMETER: - return "}"; - default: - return token.text; - } - }).collect(joining())) + .map(token -> token.text) + .collect(joining())) .collect(Collectors.toList()); } @@ -249,26 +293,4 @@ public String toString() { } - List parse(String expression) { - List tokens = tokenizer.tokenize(expression); - List ast = new ArrayList<>(); - int length = tokens.size(); - int current = 0; - while (current < length) { - boolean parsed = false; - for (Parse parser : parsers) { - int consumedChars = parser.parse(ast, tokens, current); - if (consumedChars != 0) { - current += consumedChars; - parsed = true; - break; - } - } - if (!parsed) { - // Should not happen - throw new IllegalStateException("Could not parse " + tokens); - } - } - return ast; - } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 83ec434584..6734582406 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -9,6 +9,10 @@ class CucumberExpressionTokenizer { + private interface Tokenize { + int tokenize(List tokens, String expression, int current); + } + private static final List tokenizers = Arrays.asList( tokenizePattern(Token.Type.ESCAPED_WHITE_SPACE, Pattern.compile("\\\\\\s")), tokenizePattern(Token.Type.WHITE_SPACE, Pattern.compile("\\s+")), @@ -49,8 +53,9 @@ List tokenize(String expression) { } } if (!tokenized) { - // Should not happen - throw new IllegalStateException("Could not parse " + expression); + // Can't happen if configured properly + // Leave in to avoid looping if not configured properly + throw new IllegalStateException("Could not tokenize " + expression); } } return tokens; @@ -93,10 +98,6 @@ private static Tokenize tokenizePattern(Token.Type type, Pattern pattern) { } - private interface Tokenize { - int tokenize(List tokens, String expression, int current); - } - static class Token { final String text; final Type type; From 3dfe92f31d8a5cabe22533ae48fd09dad7750310 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 10:49:50 +0100 Subject: [PATCH 032/183] Add lookahead for optional --- cucumber-expressions/README.md | 17 ++--- .../CucumberExpressionParser.java | 64 +++++++++++++------ .../CucumberExpressionParserTest.java | 30 ++++++++- .../CucumberExpressionTest.java | 2 +- 4 files changed, 80 insertions(+), 33 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index a91994879c..24f550cf27 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -4,16 +4,13 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ## Grammar ## ``` -cucumber-expression := [ optional | escaped-optional | other-then-optional ]* -optional := '(' + text + ')' -escaped-optional := '\(' + other-then-optional + ')' -other-then-optional: = [ alternative | escaped-alternative | other-then-alternative ]* -alternative := text + [ '/' + text ]+ -escaped-alternative := other-then-alternative +[ '\/' + other-then-alternative ]+ -other-then-alternative := [ parameter | escaped-parameter | other-then-parameter ]* -parameter := '{' + text + '}' -escaped-parameter := '\{' + other-then-parameter + '}' -other-then-parameter:= text +cucumber-expression := ( optional | alternatives | parameter | text )* +optional := '(' + optional-text + ')' +optional-text := [^ ')' ] +alternative := alternative-text* + ( '/' + alternative-text* )+ +alternative-text: = [^ whitespace | '(' | ')' ] +parameter := '{' + parameter-text* + '}' +parameter-text := [^ '}' ] text := .* ``` diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index c902b0c6a5..df8fe06a68 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -24,7 +24,6 @@ import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_WHITE_SPACE; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; @@ -36,6 +35,10 @@ final class CucumberExpressionParser { private static final Token END_OPTIONAL_TOKEN = new Token(")", END_OPTIONAL); private static final Token ESCAPE_TOKEN = new Token("\\", ESCAPE); private static final Token ALTERNATION_TOKEN = new Token("/", ALTERNATION); + // Rewrite the token text but retain the type. This allows Cucumber Expressions + // to validate there are no parameters in alternation but renders escapes correctly. + private static final Token ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN = new Token("{", ESCAPED_BEGIN_PARAMETER); + private static final Token ESCAPED_END_PARAMETER_TOKEN_AS_END_PARAMETER_TOKEN = new Token("}", ESCAPED_END_PARAMETER); private static final Function escapeOptional; private static final Function escapeAlternation; @@ -46,8 +49,8 @@ final class CucumberExpressionParser { static { Map> escapesInOptional = new EnumMap<>(Token.Type.class); escapesInOptional.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); - escapesInOptional.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); escapesInOptional.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); + escapesInOptional.put(ESCAPED_BEGIN_PARAMETER, token -> ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN); escapeOptional = rewrite(escapesInOptional); Map> escapesInAlternation = new EnumMap<>(Token.Type.class); @@ -56,18 +59,12 @@ final class CucumberExpressionParser { escapesInAlternation.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); escapesInAlternation.put(ESCAPED_WHITE_SPACE, token -> new Token(token.text.substring(1), WHITE_SPACE)); escapesInAlternation.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); - // Rewrite the token text but retain the type. This allows Cucumber Expressions - // to validate there are no parameters in alternation but renders escapes correctly. - escapesInAlternation.put(ESCAPED_BEGIN_PARAMETER, token -> new Token("{", ESCAPED_BEGIN_PARAMETER)); - escapesInAlternation.put(ESCAPED_END_PARAMETER, token -> new Token("}", ESCAPED_END_PARAMETER)); + escapesInAlternation.put(ESCAPED_BEGIN_PARAMETER, token -> ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN); escapeAlternation = rewrite(escapesInAlternation); Map> escapesInParameter = new EnumMap<>(Token.Type.class); escapesInParameter.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); - escapesInAlternation.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); - escapesInAlternation.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); escapesInAlternation.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); - escapesInParameter.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); escapesInParameter.put(ESCAPED_END_PARAMETER, token -> END_PARAMETER_TOKEN); escapeParameter = rewrite(escapesInParameter); @@ -118,9 +115,9 @@ List parse(String expression) { private static Parse parseText() { return (ast, expression, current) -> { - Token token = expression.get(current); - Token unescaped = escapeText.apply(token); - ast.add(new Text(singletonList(unescaped))); + Token currentToken = expression.get(current); + Token unescaped = escapeText.apply(currentToken); + ast.add(new Text(unescaped)); return 1; }; } @@ -132,16 +129,28 @@ private static Parse parseAlternation() { return 0; } - int leftHandBoundary = findFirst(expression, current, WHITE_SPACE, END_OPTIONAL); + int leftHandBoundary = findFirst(expression, current, WHITE_SPACE); if (leftHandBoundary >= 0 && leftHandBoundary < pivot) { return 0; } - int rightHandBoundary = findFirst(expression, pivot, WHITE_SPACE, BEGIN_OPTIONAL); + List leftHandSide = expression.subList(current, pivot); + int leftHandOptional = lookAHeadForOptional(leftHandSide); + if (leftHandOptional >= 0) { + return 0; + } + + int rightHandBoundary = findFirst(expression, pivot, WHITE_SPACE); if (rightHandBoundary < 0) { rightHandBoundary = expression.size(); } + List rightHandSide = expression.subList(pivot, rightHandBoundary); + int rightHandOptional = lookAHeadForOptional(rightHandSide); + if(rightHandOptional > 0){ + rightHandBoundary = pivot + rightHandOptional; + } + List tokens = expression.subList(current, rightHandBoundary); List> alternatives = split(tokens, ALTERNATION); List> unescapedAlternative = alternatives.stream() @@ -155,14 +164,27 @@ private static Parse parseAlternation() { }; } + private static int lookAHeadForOptional(List expression) { + int beginOptional = findFirst(expression, 0, BEGIN_OPTIONAL); + if (beginOptional < 0) { + return -1; + } + + int endOptional = findFirst(expression, beginOptional, END_OPTIONAL); + if (endOptional < 0) { + return -1; + } + return beginOptional; + } + private static Parse parseBetween(Token.Type startToken, Token.Type endToken, Function, Node> createNode, Function rewriteEscapes ) { return (ast, expression, current) -> { - Token token = expression.get(current); - if (token.type != startToken) { + Token currentToken = expression.get(current); + if (currentToken.type != startToken) { return 0; } @@ -217,10 +239,10 @@ static abstract class Node { static final class Text extends Node { - final List tokens; + private final Token token; - Text(List tokens) { - this.tokens = tokens; + Text(Token token) { + this.token = token; } @Override @@ -229,7 +251,7 @@ public String toString() { } String getText() { - return tokens.stream().map(token -> token.text).collect(joining()); + return token.text; } } @@ -253,7 +275,7 @@ String getOptionalText() { static final class Parameter extends Node { - final List tokens; + private final List tokens; Parameter(List tokens) { this.tokens = tokens; diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 57b84564fd..eae13f2553 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -126,7 +126,7 @@ void escapedOptionalFollowedByOptional() { @Test void optionalContainingEscapedOptional() { - assertThat(parser.parse("three (\\(very\\) blind) mice"), contains( + assertThat(parser.parse("three ((very\\) blind) mice"), contains( node("three", Text.class), node(" ", Text.class), node("(very) blind", Optional.class), @@ -171,6 +171,34 @@ void alternationWithWhiteSpace() { )); } + @Test + void alternationWithUnusedEndOptional() { + assertThat(parser.parse("three )blind\\ mice/rats"), contains( + node("three", Text.class), + node(" ", Text.class), + node(")blind mice - rats", Alternation.class) + )); + } + + @Test + void alternationWithUnusedStartOptional() { + assertThat(parser.parse("three blind\\ mice/rats("), contains( + node("three", Text.class), + node(" ", Text.class), + node("blind mice - rats(", Alternation.class) + )); + } + + @Test + void alternationFollowedByOptional() { + assertThat(parser.parse("three blind\\ rat/cat(s)"), contains( + node("three", Text.class), + node(" ", Text.class), + node("blind rat - cat", Alternation.class), + node("s", Optional.class) + )); + } + private static Matcher node(String expectedExpression, Class type) { return new TypeSafeDiagnosingMatcher() { @Override diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 8310248b53..7c15e9d117 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -140,7 +140,7 @@ public void matches_escaped_parenthesis() { assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); parameterTypeRegistry.defineParameterType(new ParameterType<>("{string}", "\"(.*)\"", String.class, (String arg) -> arg)); - assertEquals(singletonList("blind"), match("three (\\(exceptionally\\)) {\\{string\\}} mice", "three (exceptionally) \"blind\" mice")); + assertEquals(singletonList("blind"), match("three ((exceptionally\\)) {{string\\}} mice", "three (exceptionally) \"blind\" mice")); } @Test From 8e4f3d250f03dee750790d23ededfdd690367b70 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 11:17:08 +0100 Subject: [PATCH 033/183] Update grammar --- cucumber-expressions/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 24f550cf27..ca30412c98 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -4,11 +4,12 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ## Grammar ## ``` -cucumber-expression := ( optional | alternatives | parameter | text )* +cucumber-expression := ( optional | other-then-optional )* +other-then-optional: = ( alternation | parameter | text )* optional := '(' + optional-text + ')' optional-text := [^ ')' ] -alternative := alternative-text* + ( '/' + alternative-text* )+ -alternative-text: = [^ whitespace | '(' | ')' ] +alternation := alternative-text* + ( '/' + alternative-text* )+ +alternative-text: = [^ whitespace ] parameter := '{' + parameter-text* + '}' parameter-text := [^ '}' ] text := .* From 22181bc3c762b78a77d1494919b337b6d0157c8c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 14:04:13 +0100 Subject: [PATCH 034/183] Move alternation to top level --- cucumber-expressions/README.md | 11 +- .../CucumberExpression.java | 20 +- .../CucumberExpressionParser.java | 259 +++++++++--------- .../CucumberExpressionParserTest.java | 11 +- .../CucumberExpressionPatternTest.java | 4 +- .../CucumberExpressionTest.java | 22 +- 6 files changed, 179 insertions(+), 148 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index ca30412c98..965859ee00 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -4,12 +4,13 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ## Grammar ## ``` -cucumber-expression := ( optional | other-then-optional )* -other-then-optional: = ( alternation | parameter | text )* -optional := '(' + optional-text + ')' +cucumber-expression := ( alternation | optional | parameter | text)* +alternation := alternative* + ( '/' + alternative* )+ +alternative: = optional | parameter | alternative-text +alternative-text: = [^ whitespace ] +optional := '(' + option* + ')' +option := parameter | optional-text optional-text := [^ ')' ] -alternation := alternative-text* + ( '/' + alternative-text* )+ -alternative-text: = [^ whitespace ] parameter := '{' + parameter-text* + '}' parameter-text := [^ '}' ] text := .* diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 4d13131ad0..bc5d777b25 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -10,6 +10,7 @@ import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { @@ -17,6 +18,7 @@ public final class CucumberExpression implements Expression { private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String ALTERNATIVE_MAY_NOT_BE_EMPTY = "Alternative may not be empty: "; + private static final String ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS = "Alternative may not exclusively contain optionals: "; private final List> parameterTypes = new ArrayList<>(); private final String source; @@ -50,7 +52,7 @@ private String process(CucumberExpressionParser.Node node) { validateAlternation(alternation); return alternation.getAlternatives() .stream() - .map(CucumberExpression::escapeRegex) + .map(nodes -> nodes.stream().map(this::process).collect(joining())) .collect(joining("|","(?:",")")); } if (node instanceof CucumberExpressionParser.Parameter) { @@ -79,11 +81,22 @@ private void validateAlternation(CucumberExpressionParser.Alternation alternatio throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } - for (List alternative : alternation.alternatives) { + for (List alternative : alternation.alternatives) { if (alternative.isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } - checkNotParameterType(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); + boolean hasParameter = alternative.stream() + .anyMatch(node -> node instanceof CucumberExpressionParser.Parameter); + + if(hasParameter){ + throw new CucumberExpressionException(PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE + source); + } + + boolean hasTextNode = alternative.stream().anyMatch(node -> node instanceof CucumberExpressionParser.Text); + if (!hasTextNode) { + throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS + source); + } + } } @@ -91,6 +104,7 @@ private static String escapeRegex(String text) { return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); } + //TODO: Catch in AST. private void checkNotParameterType(List tokens, String message) { int beginParameterIndex = -1; int endParameterIndex = -1; diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index df8fe06a68..37f6369f56 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -6,8 +6,8 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; -import java.util.stream.Collectors; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL; @@ -38,12 +38,13 @@ final class CucumberExpressionParser { // Rewrite the token text but retain the type. This allows Cucumber Expressions // to validate there are no parameters in alternation but renders escapes correctly. private static final Token ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN = new Token("{", ESCAPED_BEGIN_PARAMETER); - private static final Token ESCAPED_END_PARAMETER_TOKEN_AS_END_PARAMETER_TOKEN = new Token("}", ESCAPED_END_PARAMETER); private static final Function escapeOptional; - private static final Function escapeAlternation; private static final Function escapeParameter; private static final Function escapeText; + private static final Function escapeAlternativeText; + + static { @@ -53,18 +54,8 @@ final class CucumberExpressionParser { escapesInOptional.put(ESCAPED_BEGIN_PARAMETER, token -> ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN); escapeOptional = rewrite(escapesInOptional); - Map> escapesInAlternation = new EnumMap<>(Token.Type.class); - escapesInAlternation.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); - escapesInAlternation.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); - escapesInAlternation.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); - escapesInAlternation.put(ESCAPED_WHITE_SPACE, token -> new Token(token.text.substring(1), WHITE_SPACE)); - escapesInAlternation.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); - escapesInAlternation.put(ESCAPED_BEGIN_PARAMETER, token -> ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN); - escapeAlternation = rewrite(escapesInAlternation); - Map> escapesInParameter = new EnumMap<>(Token.Type.class); escapesInParameter.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); - escapesInAlternation.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); escapesInParameter.put(ESCAPED_END_PARAMETER, token -> END_PARAMETER_TOKEN); escapeParameter = rewrite(escapesInParameter); @@ -74,17 +65,115 @@ final class CucumberExpressionParser { escapesInText.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); escapesInText.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); escapeText = rewrite(escapesInText); + + Map> escapesInAlternativeText = new EnumMap<>(Token.Type.class); + escapesInAlternativeText.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); + escapesInAlternativeText.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); + escapesInAlternativeText.put(ESCAPED_WHITE_SPACE, token -> new Token(token.text.substring(1), WHITE_SPACE)); + escapesInAlternativeText.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); + escapesInAlternativeText.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); + escapeAlternativeText = rewrite(escapesInAlternativeText); } private interface Parse { int parse(List ast, List expression, int current); } + private static final Parse optionalParser = (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type != BEGIN_OPTIONAL) { + return 0; + } + + int endIndex = findFirst(expression, current + 1, END_OPTIONAL); + if (endIndex <= 0) { + return 0; + } + List tokens = expression.subList(current + 1, endIndex); + List unescaped = tokens.stream().map(escapeOptional).collect(toList()); + ast.add(new Optional(unescaped)); + // Consumes end token + return endIndex + 1 - current; + + }; + private static final Parse parameterParser = (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type != BEGIN_PARAMETER) { + return 0; + } + + int endIndex = findFirst(expression, current + 1, END_PARAMETER); + if (endIndex <= 0) { + return 0; + } + List tokens = expression.subList(current + 1, endIndex); + List unescaped = tokens.stream().map(escapeParameter).collect(toList()); + ast.add(new Parameter(unescaped)); + // Consumes end token + return endIndex + 1 - current; + + }; + + private static final Parse textParser = (ast, expression, current) -> { + Token currentToken = expression.get(current); + Token unescaped = escapeText.apply(currentToken); + ast.add(new Text(unescaped)); + return 1; + }; + + private static final Parse alternativeTextParser = (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type == WHITE_SPACE) { + return 0; + } + Token unescaped = escapeAlternativeText.apply(currentToken); + ast.add(new Text(unescaped)); + return 1; + }; + + private static final Node ALTERNATION_NODE = new Node() { + + }; + private static Parse alternationSeparatorParser = (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type != ALTERNATION) { + return 0; + } + ast.add(ALTERNATION_NODE); + return 1; + }; + + private static final Parse alternationParser = (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type == WHITE_SPACE) { + return 0; + } + + List parsers = asList( + alternationSeparatorParser, + optionalParser, + parameterParser, + alternativeTextParser + ); + + List alternationAst = new ArrayList<>(); + List subExpression = expression.subList(current, expression.size()); + int consumed = parse(parsers, alternationAst, subExpression); + if (!alternationAst.contains(ALTERNATION_NODE)) { + return 0; + } + List> alternatives = split(alternationAst, ALTERNATION_NODE); + ast.add(new Alternation(alternatives)); + // Does not consume right hand boundary token + return consumed; + }; + + private static final List parsers = asList( - parseBetween(BEGIN_OPTIONAL, END_OPTIONAL, Optional::new, escapeOptional), - parseAlternation(), - parseBetween(BEGIN_PARAMETER, END_PARAMETER, Parameter::new, escapeParameter), - parseText() + alternationParser, + optionalParser, + parameterParser, + textParser ); private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); @@ -93,11 +182,20 @@ List parse(String expression) { List tokens = tokenizer.tokenize(expression); List ast = new ArrayList<>(); int length = tokens.size(); + int consumed = parse(parsers, ast, tokens); + if (consumed != length) { + throw new IllegalStateException("Could not parse " + tokens); + } + return ast; + } + + private static int parse(List parsers, List ast, List expression) { + int length = expression.size(); int current = 0; while (current < length) { boolean parsed = false; for (Parse parser : parsers) { - int consumedChars = parser.parse(ast, tokens, current); + int consumedChars = parser.parse(ast, expression, current); if (consumedChars != 0) { current += consumedChars; parsed = true; @@ -105,100 +203,10 @@ List parse(String expression) { } } if (!parsed) { - // Can't happen if configured properly - // Leave in to avoid looping if not configured properly - throw new IllegalStateException("Could not parse " + tokens); + break; } } - return ast; - } - - private static Parse parseText() { - return (ast, expression, current) -> { - Token currentToken = expression.get(current); - Token unescaped = escapeText.apply(currentToken); - ast.add(new Text(unescaped)); - return 1; - }; - } - - private static Parse parseAlternation() { - return (ast, expression, current) -> { - int pivot = findFirst(expression, current, ALTERNATION); - if (pivot < 0) { - return 0; - } - - int leftHandBoundary = findFirst(expression, current, WHITE_SPACE); - if (leftHandBoundary >= 0 && leftHandBoundary < pivot) { - return 0; - } - - List leftHandSide = expression.subList(current, pivot); - int leftHandOptional = lookAHeadForOptional(leftHandSide); - if (leftHandOptional >= 0) { - return 0; - } - - int rightHandBoundary = findFirst(expression, pivot, WHITE_SPACE); - if (rightHandBoundary < 0) { - rightHandBoundary = expression.size(); - } - - List rightHandSide = expression.subList(pivot, rightHandBoundary); - int rightHandOptional = lookAHeadForOptional(rightHandSide); - if(rightHandOptional > 0){ - rightHandBoundary = pivot + rightHandOptional; - } - - List tokens = expression.subList(current, rightHandBoundary); - List> alternatives = split(tokens, ALTERNATION); - List> unescapedAlternative = alternatives.stream() - .map(alternative -> alternative.stream() - .map(escapeAlternation) - .collect(toList()) - ).collect(toList()); - ast.add(new Alternation(unescapedAlternative)); - // Does not consume right hand boundary token - return rightHandBoundary - current; - }; - } - - private static int lookAHeadForOptional(List expression) { - int beginOptional = findFirst(expression, 0, BEGIN_OPTIONAL); - if (beginOptional < 0) { - return -1; - } - - int endOptional = findFirst(expression, beginOptional, END_OPTIONAL); - if (endOptional < 0) { - return -1; - } - return beginOptional; - } - - private static Parse parseBetween(Token.Type startToken, - Token.Type endToken, - Function, Node> createNode, - Function rewriteEscapes - ) { - return (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type != startToken) { - return 0; - } - - int endIndex = findFirst(expression, current + 1, endToken); - if (endIndex <= 0) { - return 0; - } - List tokens = expression.subList(current + 1, endIndex); - List unescaped = tokens.stream().map(rewriteEscapes).collect(toList()); - ast.add(createNode.apply(unescaped)); - // Consumes end token - return endIndex + 1 - current; - - }; + return current; } private static Function rewrite(Map> rewriteRules) { @@ -217,12 +225,12 @@ private static int findFirst(List expression, int fromIndex, Token.Type.. return -1; } - private static List> split(List tokens, Token.Type type) { - List> alternatives = new ArrayList<>(); - List alternative = new ArrayList<>(); + private static List> split(List tokens, T bound) { + List> alternatives = new ArrayList<>(); + List alternative = new ArrayList<>(); alternatives.add(alternative); - for (Token token : tokens) { - if (token.type == type) { + for (T token : tokens) { + if (bound.equals(token)) { alternative = new ArrayList<>(); alternatives.add(alternative); } else { @@ -265,7 +273,7 @@ static final class Optional extends Node { @Override public String toString() { - return getOptionalText(); + return "(" + getOptionalText() + ")"; } String getOptionalText() { @@ -293,24 +301,23 @@ String getParameterName() { static final class Alternation extends Node { - final List> alternatives; + final List> alternatives; - Alternation(List> alternatives) { + Alternation(List> alternatives) { this.alternatives = alternatives; } - List getAlternatives() { - return alternatives.stream() - .map(alternatives -> alternatives - .stream() - .map(token -> token.text) - .collect(joining())) - .collect(Collectors.toList()); + List> getAlternatives() { + return alternatives; } @Override public String toString() { - return String.join(" - ", getAlternatives()); + return getAlternatives().stream() + .map(nodes -> nodes.stream() + .map(Objects::toString) + .collect(joining())) + .collect(joining(" - ")); } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index eae13f2553..eeb6fb05b0 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -37,7 +37,7 @@ void phrase() { @Test void optional() { assertThat(parser.parse("(blind)"), contains( - node("blind", Optional.class) + node("(blind)", Optional.class) )); } @@ -60,7 +60,7 @@ void optionalPhrase() { assertThat(parser.parse("three (blind) mice"), contains( node("three", Text.class), node(" ", Text.class), - node("blind", Optional.class), + node("(blind)", Optional.class), node(" ", Text.class), node("mice", Text.class) )); @@ -115,7 +115,7 @@ void escapedOptionalFollowedByOptional() { node("three", Text.class), node(" ", Text.class), node("(", Text.class), - node("very", Optional.class), + node("(very)", Optional.class), node(" ", Text.class), node("blind", Text.class), node(")", Text.class), @@ -129,7 +129,7 @@ void optionalContainingEscapedOptional() { assertThat(parser.parse("three ((very\\) blind) mice"), contains( node("three", Text.class), node(" ", Text.class), - node("(very) blind", Optional.class), + node("((very) blind)", Optional.class), node(" ", Text.class), node("mice", Text.class) )); @@ -194,8 +194,7 @@ void alternationFollowedByOptional() { assertThat(parser.parse("three blind\\ rat/cat(s)"), contains( node("three", Text.class), node(" ", Text.class), - node("blind rat - cat", Alternation.class), - node("s", Optional.class) + node("blind rat - cat(s)", Alternation.class) )); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java index 333d91e863..d262014e48 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java @@ -39,8 +39,8 @@ public void translates_alternation_with_non_alpha() { @Test public void translates_alternation_with_optional_words() { assertPattern( - "the (test )chat/call/email interactions are visible", - "^the (?:test )?(?:chat|call|email) interactions are visible$" + "the (test )chat/(test )call/(test )email interactions are visible", + "^the (?:(?:test )?chat|(?:test )?call|(?:test )?email) interactions are visible$" ); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 7c15e9d117..f92c3cf5af 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -43,30 +43,40 @@ public void matches_alternation() { @Test public void matches_optional_before_alternation() { - assertEquals(emptyList(), match("three (brown )mice/rats", "three brown rats")); + assertEquals(emptyList(), match("three (brown )mice/rats", "three brown mice")); + assertEquals(emptyList(), match("three (brown )mice/rats", "three rats")); + } + + @Test + public void matches_optional_in_alternation() { + assertEquals(singletonList(3), match("{int} rat(s)/mouse/mice", "3 rats")); + assertEquals(singletonList(2), match("{int} rat(s)/mouse/mice", "2 mice")); + assertEquals(singletonList(1), match("{int} rat(s)/mouse/mice", "1 mouse")); } @Test public void matches_optional_before_alternation_with_regex_characters() { - assertEquals(singletonList(2), match("I wait {int} second(s)./?", "I wait 2 second?")); + assertEquals(singletonList(2), match("I wait {int} second(s)./second(s)?", "I wait 2 second?")); + assertEquals(singletonList(1), match("I wait {int} second(s)./second(s)?", "I wait 1 second.")); } @Test public void matches_alternation_in_optional_as_text() { assertEquals(emptyList(), match("three( brown/black) mice", "three brown/black mice")); } + @Test public void does_not_allow_alternation_with_empty_alternative() { - Executable testMethod = () -> match("three brown//black mice", "three brown mice"); + Executable testMethod = () -> match("three brown//black mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: three brown//black mice"))); } @Test - public void does_not_allow_optional_adjacent_to_alternation() { - Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); + public void allows_optional_adjacent_to_alternation() { + Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: three (brown)/black mice"))); + assertThat(thrownException.getMessage(), is(equalTo("Alternative may not exclusively contain optionals: three (brown)/black mice"))); } @Test From b15d190f3d802ac3defbbe2e31dac7a0d2e130a6 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 15:42:48 +0100 Subject: [PATCH 035/183] Clean up --- cucumber-expressions/README.md | 5 +- .../CucumberExpression.java | 51 +++++-------------- .../CucumberExpressionParser.java | 51 +++++++++++-------- 3 files changed, 46 insertions(+), 61 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 965859ee00..a75506f6ae 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -4,12 +4,11 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ## Grammar ## ``` -cucumber-expression := ( alternation | optional | parameter | text)* +cucumber-expression := ( alternation | optional | parameter | text )* alternation := alternative* + ( '/' + alternative* )+ alternative: = optional | parameter | alternative-text alternative-text: = [^ whitespace ] -optional := '(' + option* + ')' -option := parameter | optional-text +optional := '(' + optional-text* + ')' optional-text := [^ ')' ] parameter := '{' + parameter-text* + '}' parameter-text := [^ '}' ] diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index bc5d777b25..abccf7c945 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -1,5 +1,6 @@ package io.cucumber.cucumberexpressions; +import io.cucumber.cucumberexpressions.CucumberExpressionParser.Node; import org.apiguardian.api.API; import java.lang.reflect.Type; @@ -7,10 +8,7 @@ import java.util.List; import java.util.regex.Pattern; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { @@ -30,20 +28,22 @@ public final class CucumberExpression implements Expression { this.parameterTypeRegistry = parameterTypeRegistry; CucumberExpressionParser parser = new CucumberExpressionParser(); - List parse = parser.parse(expression); + List ast = parser.parse(expression); - String pattern = parse.stream() - .map(this::process) + String pattern = ast.stream() + .map(this::rewriteToRegex) .collect(joining("", "^", "$")); treeRegexp = new TreeRegexp(pattern); } - private String process(CucumberExpressionParser.Node node) { + private String rewriteToRegex(Node node) { if (node instanceof CucumberExpressionParser.Optional) { CucumberExpressionParser.Optional optional = (CucumberExpressionParser.Optional) node; - checkNotParameterType(optional.tokens, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); + if (optional.containsParameterType()){ + throw new CucumberExpressionException(PARAMETER_TYPES_CANNOT_BE_OPTIONAL + source); + } return "(?:" + escapeRegex(optional.getOptionalText()) + ")?"; } @@ -52,9 +52,10 @@ private String process(CucumberExpressionParser.Node node) { validateAlternation(alternation); return alternation.getAlternatives() .stream() - .map(nodes -> nodes.stream().map(this::process).collect(joining())) + .map(nodes -> nodes.stream().map(this::rewriteToRegex).collect(joining())) .collect(joining("|","(?:",")")); } + if (node instanceof CucumberExpressionParser.Parameter) { CucumberExpressionParser.Parameter parameter = (CucumberExpressionParser.Parameter) node; ParameterType.checkParameterTypeName(parameter.getParameterName()); @@ -77,26 +78,21 @@ private String process(CucumberExpressionParser.Node node) { private void validateAlternation(CucumberExpressionParser.Alternation alternation) { // Make sure the alternative parts aren't empty and don't contain parameter types - if (alternation.alternatives.isEmpty()) { - throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); - } - - for (List alternative : alternation.alternatives) { + for (List alternative : alternation.getAlternatives()) { if (alternative.isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } boolean hasParameter = alternative.stream() .anyMatch(node -> node instanceof CucumberExpressionParser.Parameter); - if(hasParameter){ throw new CucumberExpressionException(PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE + source); } - boolean hasTextNode = alternative.stream().anyMatch(node -> node instanceof CucumberExpressionParser.Text); + boolean hasTextNode = alternative.stream() + .anyMatch(node -> node instanceof CucumberExpressionParser.Text); if (!hasTextNode) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS + source); } - } } @@ -104,27 +100,6 @@ private static String escapeRegex(String text) { return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); } - //TODO: Catch in AST. - private void checkNotParameterType(List tokens, String message) { - int beginParameterIndex = -1; - int endParameterIndex = -1; - for (int i = 0; i < tokens.size(); i++) { - CucumberExpressionTokenizer.Token token = tokens.get(i); - if (beginParameterIndex == -1 && token.type == BEGIN_PARAMETER) { - beginParameterIndex = i; - } - if (endParameterIndex == -1 && token.type == END_PARAMETER) { - endParameterIndex = i; - } - } - if(beginParameterIndex == -1 || endParameterIndex == -1){ - return; - } - if (beginParameterIndex < endParameterIndex) { - throw new CucumberExpressionException(message + source); - } - } - private String buildCaptureRegexp(List regexps) { if (regexps.size() == 1) { return "(" + regexps.get(0) + ")"; diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 37f6369f56..b457abd672 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -35,18 +35,20 @@ final class CucumberExpressionParser { private static final Token END_OPTIONAL_TOKEN = new Token(")", END_OPTIONAL); private static final Token ESCAPE_TOKEN = new Token("\\", ESCAPE); private static final Token ALTERNATION_TOKEN = new Token("/", ALTERNATION); - // Rewrite the token text but retain the type. This allows Cucumber Expressions - // to validate there are no parameters in alternation but renders escapes correctly. - private static final Token ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN = new Token("{", ESCAPED_BEGIN_PARAMETER); + // Rewrite the token text but retain the type. This allows Cucumber + // Expression to validate there are no parameters in alternation but renders + // escaped braces correctly. Easier then then implementing + // + // optional := '(' + option* + ')' + // option := parameter | option-text + private static final Token ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN = + new Token("{", ESCAPED_BEGIN_PARAMETER); private static final Function escapeOptional; private static final Function escapeParameter; private static final Function escapeText; private static final Function escapeAlternativeText; - - - static { Map> escapesInOptional = new EnumMap<>(Token.Type.class); escapesInOptional.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); @@ -132,7 +134,10 @@ private interface Parse { }; private static final Node ALTERNATION_NODE = new Node() { - + // Marker. This way we don't need to model the + // the tail end of alternation in the AST: + // + // alternation := alternative* + ( '/' + alternative* )+ }; private static Parse alternationSeparatorParser = (ast, expression, current) -> { Token currentToken = expression.get(current); @@ -143,26 +148,26 @@ private interface Parse { return 1; }; + private static final List alternationParsers = asList( + alternationSeparatorParser, + optionalParser, + parameterParser, + alternativeTextParser + ); + private static final Parse alternationParser = (ast, expression, current) -> { Token currentToken = expression.get(current); if (currentToken.type == WHITE_SPACE) { return 0; } - List parsers = asList( - alternationSeparatorParser, - optionalParser, - parameterParser, - alternativeTextParser - ); - List alternationAst = new ArrayList<>(); List subExpression = expression.subList(current, expression.size()); - int consumed = parse(parsers, alternationAst, subExpression); + int consumed = parse(alternationParsers, alternationAst, subExpression); if (!alternationAst.contains(ALTERNATION_NODE)) { return 0; } - List> alternatives = split(alternationAst, ALTERNATION_NODE); + List> alternatives = splitOnAlternation(alternationAst); ast.add(new Alternation(alternatives)); // Does not consume right hand boundary token return consumed; @@ -225,12 +230,12 @@ private static int findFirst(List expression, int fromIndex, Token.Type.. return -1; } - private static List> split(List tokens, T bound) { + private static List> splitOnAlternation(List tokens) { List> alternatives = new ArrayList<>(); List alternative = new ArrayList<>(); alternatives.add(alternative); for (T token : tokens) { - if (bound.equals(token)) { + if (ALTERNATION_NODE.equals(token)) { alternative = new ArrayList<>(); alternatives.add(alternative); } else { @@ -265,7 +270,7 @@ String getText() { static final class Optional extends Node { - final List tokens; + private final List tokens; Optional(List tokens) { this.tokens = tokens; @@ -279,6 +284,12 @@ public String toString() { String getOptionalText() { return tokens.stream().map(token -> token.text).collect(joining()); } + + boolean containsParameterType() { + List ast = new ArrayList<>(); + parameterParser.parse(ast, tokens, 0); + return ast.stream().anyMatch(node -> node instanceof Parameter); + } } static final class Parameter extends Node { @@ -301,7 +312,7 @@ String getParameterName() { static final class Alternation extends Node { - final List> alternatives; + private final List> alternatives; Alternation(List> alternatives) { this.alternatives = alternatives; From 305fc1fb2db4bcf81e8082aa8ced6fb066203aad Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 16:22:55 +0100 Subject: [PATCH 036/183] Fiddling --- cucumber-expressions/README.md | 18 +++-- .../CucumberExpressionParser.java | 65 +++++++++---------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index a75506f6ae..0db1116072 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -5,16 +5,20 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ``` cucumber-expression := ( alternation | optional | parameter | text )* -alternation := alternative* + ( '/' + alternative* )+ -alternative: = optional | parameter | alternative-text -alternative-text: = [^ whitespace ] -optional := '(' + optional-text* + ')' -optional-text := [^ ')' ] -parameter := '{' + parameter-text* + '}' -parameter-text := [^ '}' ] +alternation := boundry + alternative* + ( '/' + alternative* )+ + boundry +boundry := whitespace | ^ | $ +alternative: = optional | parameter | text +optional := '(' + option* + ')' +option := parameter | text +parameter := '{' + text* + '}' text := .* ``` +Note: + * `boundry` is not consumed in parsing + * While `parameter` is allowed to appear as part of `alternative` and +`option` in the AST, such an AST is not a valid a Cucumber Expression. + ## Acknowledgements The Cucumber Expression syntax is inspired by similar expression syntaxes in diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index b457abd672..d077c5413e 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -39,8 +39,13 @@ final class CucumberExpressionParser { // Expression to validate there are no parameters in alternation but renders // escaped braces correctly. Easier then then implementing // - // optional := '(' + option* + ')' + // optional := '(' + option* + ')' // option := parameter | option-text + // option-text := [^ ')' ] + // + // Instead we implement: + // + // optional := '(' + option* + ')' private static final Token ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN = new Token("{", ESCAPED_BEGIN_PARAMETER); @@ -81,40 +86,33 @@ private interface Parse { int parse(List ast, List expression, int current); } - private static final Parse optionalParser = (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type != BEGIN_OPTIONAL) { - return 0; - } + private static final Parse optionalParser = + parseBetween(BEGIN_OPTIONAL, END_OPTIONAL, escapeOptional, Optional::new); - int endIndex = findFirst(expression, current + 1, END_OPTIONAL); - if (endIndex <= 0) { - return 0; - } - List tokens = expression.subList(current + 1, endIndex); - List unescaped = tokens.stream().map(escapeOptional).collect(toList()); - ast.add(new Optional(unescaped)); - // Consumes end token - return endIndex + 1 - current; + private static final Parse parameterParser = + parseBetween(BEGIN_PARAMETER, END_PARAMETER, escapeParameter, Parameter::new); - }; - private static final Parse parameterParser = (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type != BEGIN_PARAMETER) { - return 0; - } - - int endIndex = findFirst(expression, current + 1, END_PARAMETER); - if (endIndex <= 0) { - return 0; - } - List tokens = expression.subList(current + 1, endIndex); - List unescaped = tokens.stream().map(escapeParameter).collect(toList()); - ast.add(new Parameter(unescaped)); - // Consumes end token - return endIndex + 1 - current; + private static Parse parseBetween(Token.Type startToken, + Token.Type endToken, + Function escape, + Function, Node> create) { + return (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type != startToken) { + return 0; + } - }; + int endIndex = findFirst(expression, current + 1, endToken); + if (endIndex <= 0) { + return 0; + } + List tokens = expression.subList(current + 1, endIndex); + List unescaped = tokens.stream().map(escape).collect(toList()); + ast.add(create.apply(unescaped)); + // Consumes end token + return endIndex + 1 - current; + }; + } private static final Parse textParser = (ast, expression, current) -> { Token currentToken = expression.get(current); @@ -139,6 +137,7 @@ private interface Parse { // // alternation := alternative* + ( '/' + alternative* )+ }; + private static Parse alternationSeparatorParser = (ast, expression, current) -> { Token currentToken = expression.get(current); if (currentToken.type != ALTERNATION) { @@ -173,7 +172,6 @@ private interface Parse { return consumed; }; - private static final List parsers = asList( alternationParser, optionalParser, @@ -245,7 +243,6 @@ private static List> splitOnAlternation(List tokens) { return alternatives; } - static abstract class Node { } From 9d554b030504822de3820554eda2eb745b6ae33e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 17:57:33 +0100 Subject: [PATCH 037/183] Allow parameter under optional --- cucumber-expressions/README.md | 8 +- .../CucumberExpression.java | 69 +++--- .../CucumberExpressionParser.java | 207 +++++++++--------- .../CucumberExpressionParserTest.java | 17 +- .../CucumberExpressionTest.java | 7 + 5 files changed, 163 insertions(+), 145 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 0db1116072..1e88a485e1 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -5,18 +5,16 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ``` cucumber-expression := ( alternation | optional | parameter | text )* -alternation := boundry + alternative* + ( '/' + alternative* )+ + boundry +alternation := (?:boundry) + alternative* + ( '/' + alternative* )+ + (?:boundry) boundry := whitespace | ^ | $ alternative: = optional | parameter | text optional := '(' + option* + ')' option := parameter | text parameter := '{' + text* + '}' -text := .* +text := . ``` -Note: - * `boundry` is not consumed in parsing - * While `parameter` is allowed to appear as part of `alternative` and +Note: While `parameter` is allowed to appear as part of `alternative` and `option` in the AST, such an AST is not a valid a Cucumber Expression. ## Acknowledgements diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index abccf7c945..3482cc150f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -1,6 +1,8 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.CucumberExpressionParser.Node; +import io.cucumber.cucumberexpressions.CucumberExpressionParser.Parameter; +import io.cucumber.cucumberexpressions.CucumberExpressionParser.Text; import org.apiguardian.api.API; import java.lang.reflect.Type; @@ -16,6 +18,7 @@ public final class CucumberExpression implements Expression { private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; private static final String ALTERNATIVE_MAY_NOT_BE_EMPTY = "Alternative may not be empty: "; + private static final String OPTIONAL_MAY_NOT_BE_EMPTY = "Optional may not be empty: "; private static final String ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS = "Alternative may not exclusively contain optionals: "; private final List> parameterTypes = new ArrayList<>(); @@ -41,10 +44,11 @@ public final class CucumberExpression implements Expression { private String rewriteToRegex(Node node) { if (node instanceof CucumberExpressionParser.Optional) { CucumberExpressionParser.Optional optional = (CucumberExpressionParser.Optional) node; - if (optional.containsParameterType()){ - throw new CucumberExpressionException(PARAMETER_TYPES_CANNOT_BE_OPTIONAL + source); - } - return "(?:" + escapeRegex(optional.getOptionalText()) + ")?"; + assertNoParameters(optional.getOptional(), PARAMETER_TYPES_CANNOT_BE_OPTIONAL); + assertNotEmpty(optional.getOptional(), OPTIONAL_MAY_NOT_BE_EMPTY); + return optional.getOptional().stream() + .map(this::rewriteToRegex) + .collect(joining("", "(?:", ")?")); } if (node instanceof CucumberExpressionParser.Alternation) { @@ -52,12 +56,14 @@ private String rewriteToRegex(Node node) { validateAlternation(alternation); return alternation.getAlternatives() .stream() - .map(nodes -> nodes.stream().map(this::rewriteToRegex).collect(joining())) - .collect(joining("|","(?:",")")); + .map(nodes -> nodes.stream() + .map(this::rewriteToRegex) + .collect(joining())) + .collect(joining("|", "(?:", ")")); } - if (node instanceof CucumberExpressionParser.Parameter) { - CucumberExpressionParser.Parameter parameter = (CucumberExpressionParser.Parameter) node; + if (node instanceof Parameter) { + Parameter parameter = (Parameter) node; ParameterType.checkParameterTypeName(parameter.getParameterName()); String typeName = parameter.getParameterName(); ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); @@ -65,34 +71,47 @@ private String rewriteToRegex(Node node) { throw new UndefinedParameterTypeException(typeName); } parameterTypes.add(parameterType); - return buildCaptureRegexp(parameterType.getRegexps()); + List regexps = parameterType.getRegexps(); + if (regexps.size() == 1) { + return "(" + regexps.get(0) + ")"; + } + return regexps.stream() + .collect(joining(")|(?:", "((?:", "))")); } - if (node instanceof CucumberExpressionParser.Text) { - CucumberExpressionParser.Text text = (CucumberExpressionParser.Text) node; + if (node instanceof Text) { + Text text = (Text) node; return escapeRegex(text.getText()); } throw new IllegalArgumentException(node.getClass().getName()); } + private void assertNotEmpty(List nodes, String message) { + boolean hasTextNode = nodes + .stream() + .anyMatch(Text.class::isInstance); + if (!hasTextNode) { + throw new CucumberExpressionException(message + source); + } + } + private void validateAlternation(CucumberExpressionParser.Alternation alternation) { // Make sure the alternative parts aren't empty and don't contain parameter types for (List alternative : alternation.getAlternatives()) { if (alternative.isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } - boolean hasParameter = alternative.stream() - .anyMatch(node -> node instanceof CucumberExpressionParser.Parameter); - if(hasParameter){ - throw new CucumberExpressionException(PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE + source); - } + assertNoParameters(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); + assertNotEmpty(alternative, ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS); + } + } - boolean hasTextNode = alternative.stream() - .anyMatch(node -> node instanceof CucumberExpressionParser.Text); - if (!hasTextNode) { - throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS + source); - } + private void assertNoParameters(List alternative, String parameterTypesCannotBeAlternative) { + boolean hasParameter = alternative.stream() + .anyMatch(Parameter.class::isInstance); + if (hasParameter) { + throw new CucumberExpressionException(parameterTypesCannotBeAlternative + source); } } @@ -100,14 +119,6 @@ private static String escapeRegex(String text) { return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); } - private String buildCaptureRegexp(List regexps) { - if (regexps.size() == 1) { - return "(" + regexps.get(0) + ")"; - } - return regexps.stream() - .collect(joining(")|(?:", "((?:", "))")); - } - @Override public List> match(String text, Type... typeHints) { final Group group = treeRegexp.match(text); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index d077c5413e..de5faacece 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -35,84 +35,48 @@ final class CucumberExpressionParser { private static final Token END_OPTIONAL_TOKEN = new Token(")", END_OPTIONAL); private static final Token ESCAPE_TOKEN = new Token("\\", ESCAPE); private static final Token ALTERNATION_TOKEN = new Token("/", ALTERNATION); - // Rewrite the token text but retain the type. This allows Cucumber - // Expression to validate there are no parameters in alternation but renders - // escaped braces correctly. Easier then then implementing - // - // optional := '(' + option* + ')' - // option := parameter | option-text - // option-text := [^ ')' ] - // - // Instead we implement: - // - // optional := '(' + option* + ')' - private static final Token ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN = - new Token("{", ESCAPED_BEGIN_PARAMETER); - - private static final Function escapeOptional; private static final Function escapeParameter; private static final Function escapeText; - private static final Function escapeAlternativeText; static { - Map> escapesInOptional = new EnumMap<>(Token.Type.class); - escapesInOptional.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); - escapesInOptional.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); - escapesInOptional.put(ESCAPED_BEGIN_PARAMETER, token -> ESCAPED_BEGIN_PARAMETER_TOKEN_AS_BEGIN_PARAMETER_TOKEN); - escapeOptional = rewrite(escapesInOptional); - Map> escapesInParameter = new EnumMap<>(Token.Type.class); escapesInParameter.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); + escapesInParameter.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); escapesInParameter.put(ESCAPED_END_PARAMETER, token -> END_PARAMETER_TOKEN); escapeParameter = rewrite(escapesInParameter); Map> escapesInText = new EnumMap<>(Token.Type.class); escapesInText.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); + escapesInText.put(ESCAPED_WHITE_SPACE, token -> new Token(token.text.substring(1), WHITE_SPACE)); escapesInText.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); + escapesInText.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); escapesInText.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); escapesInText.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); + escapesInText.put(ESCAPED_END_PARAMETER, token -> END_PARAMETER_TOKEN); escapeText = rewrite(escapesInText); - - Map> escapesInAlternativeText = new EnumMap<>(Token.Type.class); - escapesInAlternativeText.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); - escapesInAlternativeText.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); - escapesInAlternativeText.put(ESCAPED_WHITE_SPACE, token -> new Token(token.text.substring(1), WHITE_SPACE)); - escapesInAlternativeText.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); - escapesInAlternativeText.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); - escapeAlternativeText = rewrite(escapesInAlternativeText); } private interface Parse { int parse(List ast, List expression, int current); } - private static final Parse optionalParser = - parseBetween(BEGIN_OPTIONAL, END_OPTIONAL, escapeOptional, Optional::new); - private static final Parse parameterParser = - parseBetween(BEGIN_PARAMETER, END_PARAMETER, escapeParameter, Parameter::new); - - private static Parse parseBetween(Token.Type startToken, - Token.Type endToken, - Function escape, - Function, Node> create) { - return (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type != startToken) { - return 0; - } + (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type != BEGIN_PARAMETER) { + return 0; + } - int endIndex = findFirst(expression, current + 1, endToken); - if (endIndex <= 0) { - return 0; - } - List tokens = expression.subList(current + 1, endIndex); - List unescaped = tokens.stream().map(escape).collect(toList()); - ast.add(create.apply(unescaped)); - // Consumes end token - return endIndex + 1 - current; - }; - } + int endIndex = findFirst(expression, current + 1, END_PARAMETER); + if (endIndex <= 0) { + return 0; + } + List tokens = expression.subList(current + 1, endIndex); + List unescaped = tokens.stream().map(escapeParameter).collect(toList()); + ast.add(new Parameter(unescaped)); + // Consumes end token + return endIndex + 1 - current; + }; private static final Parse textParser = (ast, expression, current) -> { Token currentToken = expression.get(current); @@ -121,16 +85,6 @@ private static Parse parseBetween(Token.Type startToken, return 1; }; - private static final Parse alternativeTextParser = (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type == WHITE_SPACE) { - return 0; - } - Token unescaped = escapeAlternativeText.apply(currentToken); - ast.add(new Text(unescaped)); - return 1; - }; - private static final Node ALTERNATION_NODE = new Node() { // Marker. This way we don't need to model the // the tail end of alternation in the AST: @@ -147,29 +101,75 @@ private static Parse parseBetween(Token.Type startToken, return 1; }; + private static final List optionalParsers = asList( + parameterParser, + textParser + ); + + private static final Parse optionalParser = (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type != BEGIN_OPTIONAL) { + return 0; + } + List subAst = new ArrayList<>(); + int subCurrent = current + 1; + while (subCurrent < expression.size()) { + Token subCurrentToken = expression.get(subCurrent); + if (subCurrentToken.type == END_OPTIONAL) { + break; + } + // textParser in optionalParsers always consumes + // no need to check consumption + int consumed = parseToken(optionalParsers, subAst, expression, subCurrent); + subCurrent += consumed; + } + + // End not found + if(subCurrent == expression.size()){ + return 0; + } + + ast.add(new Optional(subAst)); + // Consumes right hand boundary token + return subCurrent + 1 - current; + }; + private static final List alternationParsers = asList( alternationSeparatorParser, optionalParser, parameterParser, - alternativeTextParser + textParser ); private static final Parse alternationParser = (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type == WHITE_SPACE) { - return 0; + if (current > 0) { + Token previousToken = expression.get(current - 1); + if (previousToken.type != WHITE_SPACE) { + return 0; + } } - List alternationAst = new ArrayList<>(); - List subExpression = expression.subList(current, expression.size()); - int consumed = parse(alternationParsers, alternationAst, subExpression); - if (!alternationAst.contains(ALTERNATION_NODE)) { + List subAst = new ArrayList<>(); + + int subCurrent = current; + while (subCurrent < expression.size()) { + Token currentToken = expression.get(subCurrent); + if (currentToken.type == WHITE_SPACE) { + break; + } + // textParser in alternationParsers always consumes + // no need to check consumption + int consumed = parseToken(alternationParsers, subAst, expression, subCurrent); + subCurrent += consumed; + } + + if (!subAst.contains(ALTERNATION_NODE)) { return 0; } - List> alternatives = splitOnAlternation(alternationAst); + List> alternatives = splitOnAlternation(subAst); ast.add(new Alternation(alternatives)); // Does not consume right hand boundary token - return consumed; + return subCurrent - current; }; private static final List parsers = asList( @@ -185,31 +185,29 @@ List parse(String expression) { List tokens = tokenizer.tokenize(expression); List ast = new ArrayList<>(); int length = tokens.size(); - int consumed = parse(parsers, ast, tokens); - if (consumed != length) { - throw new IllegalStateException("Could not parse " + tokens); + int current = 0; + while (current < length) { + int consumed = parseToken(parsers, ast, tokens, current); + current += consumed; + // If configured correctly this will never happen + // Keep avoid infinite loop just in case + if (consumed == 0) { + throw new IllegalStateException("Could not parse " + tokens); + } } return ast; } - private static int parse(List parsers, List ast, List expression) { - int length = expression.size(); - int current = 0; - while (current < length) { - boolean parsed = false; - for (Parse parser : parsers) { - int consumedChars = parser.parse(ast, expression, current); - if (consumedChars != 0) { - current += consumedChars; - parsed = true; - break; - } - } - if (!parsed) { + private static int parseToken(List parsers, List ast, List expression, int startAt) { + int current = startAt; + for (Parse parser : parsers) { + int consumedChars = parser.parse(ast, expression, current); + if (consumedChars != 0) { + current += consumedChars; break; } } - return current; + return current - startAt; } private static Function rewrite(Map> rewriteRules) { @@ -267,26 +265,23 @@ String getText() { static final class Optional extends Node { - private final List tokens; + private final List optional; - Optional(List tokens) { - this.tokens = tokens; + Optional(List optional) { + this.optional = optional; } - @Override - public String toString() { - return "(" + getOptionalText() + ")"; + public List getOptional() { + return optional; } - String getOptionalText() { - return tokens.stream().map(token -> token.text).collect(joining()); + @Override + public String toString() { + return optional.stream() + .map(Object::toString) + .collect(joining()); } - boolean containsParameterType() { - List ast = new ArrayList<>(); - parameterParser.parse(ast, tokens, 0); - return ast.stream().anyMatch(node -> node instanceof Parameter); - } } static final class Parameter extends Node { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index eeb6fb05b0..6f34ea981d 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -37,7 +37,7 @@ void phrase() { @Test void optional() { assertThat(parser.parse("(blind)"), contains( - node("(blind)", Optional.class) + node("blind", Optional.class) )); } @@ -60,7 +60,7 @@ void optionalPhrase() { assertThat(parser.parse("three (blind) mice"), contains( node("three", Text.class), node(" ", Text.class), - node("(blind)", Optional.class), + node("blind", Optional.class), node(" ", Text.class), node("mice", Text.class) )); @@ -73,6 +73,13 @@ void slash() { )); } + @Test + void brace() { + assertThat(parser.parse("{"), contains( + node("{", Text.class) + )); + } + @Test void openingParenthesis() { assertThat(parser.parse("("), contains( @@ -115,7 +122,7 @@ void escapedOptionalFollowedByOptional() { node("three", Text.class), node(" ", Text.class), node("(", Text.class), - node("(very)", Optional.class), + node("very", Optional.class), node(" ", Text.class), node("blind", Text.class), node(")", Text.class), @@ -129,7 +136,7 @@ void optionalContainingEscapedOptional() { assertThat(parser.parse("three ((very\\) blind) mice"), contains( node("three", Text.class), node(" ", Text.class), - node("((very) blind)", Optional.class), + node("(very) blind", Optional.class), node(" ", Text.class), node("mice", Text.class) )); @@ -194,7 +201,7 @@ void alternationFollowedByOptional() { assertThat(parser.parse("three blind\\ rat/cat(s)"), contains( node("three", Text.class), node(" ", Text.class), - node("blind rat - cat(s)", Alternation.class) + node("blind rat - cats", Alternation.class) )); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index f92c3cf5af..0eafa47454 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -65,6 +65,13 @@ public void matches_alternation_in_optional_as_text() { assertEquals(emptyList(), match("three( brown/black) mice", "three brown/black mice")); } + @Test + public void does_not_allow_empty_optional() { + Executable testMethod = () -> match("three () mice", "three brown mice"); + CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); + assertThat(thrownException.getMessage(), is(equalTo("Optional may not be empty: three () mice"))); + } + @Test public void does_not_allow_alternation_with_empty_alternative() { Executable testMethod = () -> match("three brown//black mice", "three brown mice"); From 4c8d3761a00cb0ccdd27a4a91f92eaf9549e887f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 19:37:31 +0100 Subject: [PATCH 038/183] Clean up --- .../CucumberExpression.java | 8 +- .../CucumberExpressionParser.java | 258 +++++++++--------- .../CucumberExpressionTokenizer.java | 55 ++-- .../CucumberExpressionTokenizerTest.java | 26 +- 4 files changed, 170 insertions(+), 177 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 3482cc150f..ff82a98e64 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -64,11 +64,11 @@ private String rewriteToRegex(Node node) { if (node instanceof Parameter) { Parameter parameter = (Parameter) node; - ParameterType.checkParameterTypeName(parameter.getParameterName()); - String typeName = parameter.getParameterName(); - ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(typeName); + String name = parameter.getParameterName(); + ParameterType.checkParameterTypeName(name); + ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); if (parameterType == null) { - throw new UndefinedParameterTypeException(typeName); + throw new UndefinedParameterTypeException(name); } parameterTypes.add(parameterType); List regexps = parameterType.getRegexps(); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index de5faacece..029dd50044 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -10,49 +10,44 @@ import java.util.function.Function; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPE; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_ALTERNATION; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_END_OPTIONAL; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_END_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_ESCAPE; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_WHITE_SPACE; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPE_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE_ESCAPED; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.function.Function.identity; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; final class CucumberExpressionParser { - private static final Token BEGIN_PARAMETER_TOKEN = new Token("{", BEGIN_PARAMETER); - private static final Token END_PARAMETER_TOKEN = new Token("}", END_PARAMETER); - private static final Token BEGIN_OPTIONAL_TOKEN = new Token("(", BEGIN_OPTIONAL); - private static final Token END_OPTIONAL_TOKEN = new Token(")", END_OPTIONAL); - private static final Token ESCAPE_TOKEN = new Token("\\", ESCAPE); - private static final Token ALTERNATION_TOKEN = new Token("/", ALTERNATION); - private static final Function escapeParameter; + private static final Token TOKEN_BEGIN_PARAMETER = new Token("{", BEGIN_PARAMETER); + private static final Token TOKEN_END_PARAMETER = new Token("}", END_PARAMETER); + private static final Token TOKEN_BEGIN_OPTIONAL = new Token("(", BEGIN_OPTIONAL); + private static final Token TOKEN_END_OPTIONAL = new Token(")", END_OPTIONAL); + private static final Token TOKEN_ESCAPE = new Token("\\", ESCAPE); + private static final Token TOKEN_ALTERNATION = new Token("/", ALTERNATION); + private static final Function escapeText; static { - Map> escapesInParameter = new EnumMap<>(Token.Type.class); - escapesInParameter.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); - escapesInParameter.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); - escapesInParameter.put(ESCAPED_END_PARAMETER, token -> END_PARAMETER_TOKEN); - escapeParameter = rewrite(escapesInParameter); - Map> escapesInText = new EnumMap<>(Token.Type.class); - escapesInText.put(ESCAPED_ESCAPE, token -> ESCAPE_TOKEN); - escapesInText.put(ESCAPED_WHITE_SPACE, token -> new Token(token.text.substring(1), WHITE_SPACE)); - escapesInText.put(ESCAPED_BEGIN_OPTIONAL, token -> BEGIN_OPTIONAL_TOKEN); - escapesInText.put(ESCAPED_END_OPTIONAL, token -> END_OPTIONAL_TOKEN); - escapesInText.put(ESCAPED_ALTERNATION, token -> ALTERNATION_TOKEN); - escapesInText.put(ESCAPED_BEGIN_PARAMETER, token -> BEGIN_PARAMETER_TOKEN); - escapesInText.put(ESCAPED_END_PARAMETER, token -> END_PARAMETER_TOKEN); + escapesInText.put(ESCAPE_ESCAPED, token -> TOKEN_ESCAPE); + escapesInText.put(WHITE_SPACE_ESCAPED, token -> new Token(token.text.substring(1), WHITE_SPACE)); + escapesInText.put(BEGIN_OPTIONAL_ESCAPED, token -> TOKEN_BEGIN_OPTIONAL); + escapesInText.put(END_OPTIONAL_ESCAPED, token -> TOKEN_END_OPTIONAL); + escapesInText.put(ALTERNATION_ESCAPED, token -> TOKEN_ALTERNATION); + escapesInText.put(BEGIN_PARAMETER_ESCAPED, token -> TOKEN_BEGIN_PARAMETER); + escapesInText.put(END_PARAMETER_ESCAPED, token -> TOKEN_END_PARAMETER); escapeText = rewrite(escapesInText); } @@ -60,24 +55,9 @@ private interface Parse { int parse(List ast, List expression, int current); } - private static final Parse parameterParser = - (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type != BEGIN_PARAMETER) { - return 0; - } - - int endIndex = findFirst(expression, current + 1, END_PARAMETER); - if (endIndex <= 0) { - return 0; - } - List tokens = expression.subList(current + 1, endIndex); - List unescaped = tokens.stream().map(escapeParameter).collect(toList()); - ast.add(new Parameter(unescaped)); - // Consumes end token - return endIndex + 1 - current; - }; - + /* + * text := . + */ private static final Parse textParser = (ast, expression, current) -> { Token currentToken = expression.get(current); Token unescaped = escapeText.apply(currentToken); @@ -85,7 +65,57 @@ private interface Parse { return 1; }; - private static final Node ALTERNATION_NODE = new Node() { + /* + * parameter := '{' + text* + '}' + */ + private static final Parse parameterParser = parseBetween( + BEGIN_PARAMETER, + END_PARAMETER, + singletonList(textParser), + Parameter::new + ); + + /* + * optional := '(' + option* + ')' + * option := parameter | text + */ + private static final Parse optionalParser = parseBetween( + BEGIN_OPTIONAL, + END_OPTIONAL, + asList(parameterParser, textParser), + Optional::new + ); + + private static Parse parseBetween( + Token.Type beginToken, + Token.Type endToken, + List parsers, + Function, Node> create) { + return (ast, expression, current) -> { + Token currentToken = expression.get(current); + if (currentToken.type != beginToken) { + return 0; + } + List subAst = new ArrayList<>(); + int subCurrent = current + 1; + int consumed = parseTokensUntil(parsers, subAst, expression, subCurrent, endToken); + subCurrent += consumed; + + // endToken not found + if (subCurrent == expression.size()) { + return 0; + } + Token end = expression.get(subCurrent); + if (end.type != endToken) { + return 0; + } + ast.add(create.apply(subAst)); + // consumes endToken + return subCurrent + 1 - current; + }; + } + + private static final Node NODE_ALTERNATION = new Node() { // Marker. This way we don't need to model the // the tail end of alternation in the AST: // @@ -97,43 +127,10 @@ private interface Parse { if (currentToken.type != ALTERNATION) { return 0; } - ast.add(ALTERNATION_NODE); + ast.add(NODE_ALTERNATION); return 1; }; - private static final List optionalParsers = asList( - parameterParser, - textParser - ); - - private static final Parse optionalParser = (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type != BEGIN_OPTIONAL) { - return 0; - } - List subAst = new ArrayList<>(); - int subCurrent = current + 1; - while (subCurrent < expression.size()) { - Token subCurrentToken = expression.get(subCurrent); - if (subCurrentToken.type == END_OPTIONAL) { - break; - } - // textParser in optionalParsers always consumes - // no need to check consumption - int consumed = parseToken(optionalParsers, subAst, expression, subCurrent); - subCurrent += consumed; - } - - // End not found - if(subCurrent == expression.size()){ - return 0; - } - - ast.add(new Optional(subAst)); - // Consumes right hand boundary token - return subCurrent + 1 - current; - }; - private static final List alternationParsers = asList( alternationSeparatorParser, optionalParser, @@ -141,6 +138,11 @@ private interface Parse { textParser ); + /* + * alternation := (?:boundry) + alternative* + ( '/' + alternative* )+ + (?:boundry) + * boundry := whitespace | ^ | $ + * alternative: = optional | parameter | text + */ private static final Parse alternationParser = (ast, expression, current) -> { if (current > 0) { Token previousToken = expression.get(current - 1); @@ -150,29 +152,18 @@ private interface Parse { } List subAst = new ArrayList<>(); - - int subCurrent = current; - while (subCurrent < expression.size()) { - Token currentToken = expression.get(subCurrent); - if (currentToken.type == WHITE_SPACE) { - break; - } - // textParser in alternationParsers always consumes - // no need to check consumption - int consumed = parseToken(alternationParsers, subAst, expression, subCurrent); - subCurrent += consumed; - } - - if (!subAst.contains(ALTERNATION_NODE)) { + int consumed = parseTokensUntil(alternationParsers, subAst, expression, current, WHITE_SPACE); + if (!subAst.contains(NODE_ALTERNATION)) { return 0; } + List> alternatives = splitOnAlternation(subAst); ast.add(new Alternation(alternatives)); // Does not consume right hand boundary token - return subCurrent - current; + return consumed; }; - private static final List parsers = asList( + private static final List cucumberExpressionParsers = asList( alternationParser, optionalParser, parameterParser, @@ -181,29 +172,47 @@ private interface Parse { private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + /* + * cucumber-expression := ( alternation | optional | parameter | text )* + */ List parse(String expression) { List tokens = tokenizer.tokenize(expression); List ast = new ArrayList<>(); - int length = tokens.size(); - int current = 0; - while (current < length) { - int consumed = parseToken(parsers, ast, tokens, current); - current += consumed; - // If configured correctly this will never happen - // Keep avoid infinite loop just in case + parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, null); + return ast; + } + + private static int parseTokensUntil(List parsers, + List ast, + List expression, + int startAt, + Token.Type endToken) { + int current = startAt; + while (current < expression.size()) { + Token currentToken = expression.get(current); + if (currentToken.type == endToken) { + break; + } + int consumed = parseToken(parsers, ast, expression, current); if (consumed == 0) { - throw new IllegalStateException("Could not parse " + tokens); + // If configured correctly this will never happen + // Keep to avoid infinite loop just in case + throw new IllegalStateException("Could not parse " + expression); } + current += consumed; } - return ast; + return current - startAt; } - private static int parseToken(List parsers, List ast, List expression, int startAt) { + private static int parseToken(List parsers, + List ast, + List expression, + int startAt) { int current = startAt; for (Parse parser : parsers) { - int consumedChars = parser.parse(ast, expression, current); - if (consumedChars != 0) { - current += consumedChars; + int consumed = parser.parse(ast, expression, current); + if (consumed != 0) { + current += consumed; break; } } @@ -211,19 +220,7 @@ private static int parseToken(List parsers, List ast, List e } private static Function rewrite(Map> rewriteRules) { - return token -> rewriteRules.computeIfAbsent(token.type, type -> Function.identity()).apply(token); - } - - private static int findFirst(List expression, int fromIndex, Token.Type... end) { - for (int i = fromIndex; i < expression.size(); i++) { - Token candidate = expression.get(i); - for (Token.Type type : end) { - if (candidate.type == type) { - return i; - } - } - } - return -1; + return token -> rewriteRules.computeIfAbsent(token.type, type -> identity()).apply(token); } private static List> splitOnAlternation(List tokens) { @@ -231,7 +228,7 @@ private static List> splitOnAlternation(List tokens) { List alternative = new ArrayList<>(); alternatives.add(alternative); for (T token : tokens) { - if (ALTERNATION_NODE.equals(token)) { + if (NODE_ALTERNATION.equals(token)) { alternative = new ArrayList<>(); alternatives.add(alternative); } else { @@ -281,15 +278,14 @@ public String toString() { .map(Object::toString) .collect(joining()); } - } static final class Parameter extends Node { - private final List tokens; + private final List nodes; - Parameter(List tokens) { - this.tokens = tokens; + Parameter(List nodes) { + this.nodes = nodes; } @Override @@ -298,7 +294,10 @@ public String toString() { } String getParameterName() { - return tokens.stream().map(token -> token.text).collect(joining()); + return nodes.stream() + .map(Text.class::cast) + .map(Text::getText) + .collect(joining()); } } @@ -322,7 +321,6 @@ public String toString() { .collect(joining())) .collect(joining(" - ")); } - } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 6734582406..92108bbf2a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -7,6 +7,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.*; + class CucumberExpressionTokenizer { private interface Tokenize { @@ -14,28 +16,28 @@ private interface Tokenize { } private static final List tokenizers = Arrays.asList( - tokenizePattern(Token.Type.ESCAPED_WHITE_SPACE, Pattern.compile("\\\\\\s")), - tokenizePattern(Token.Type.WHITE_SPACE, Pattern.compile("\\s+")), + tokenizePattern(WHITE_SPACE_ESCAPED, Pattern.compile("\\\\\\s")), + tokenizePattern(WHITE_SPACE, Pattern.compile("\\s+")), - tokenizeString(Token.Type.ESCAPED_BEGIN_OPTIONAL, "\\("), - tokenizeCharacter(Token.Type.BEGIN_OPTIONAL, '('), + tokenizeString(BEGIN_OPTIONAL_ESCAPED, "\\("), + tokenizeCharacter(BEGIN_OPTIONAL, '('), - tokenizeString(Token.Type.ESCAPED_END_OPTIONAL, "\\)"), - tokenizeCharacter(Token.Type.END_OPTIONAL, ')'), + tokenizeString(END_OPTIONAL_ESCAPED, "\\)"), + tokenizeCharacter(END_OPTIONAL, ')'), - tokenizeString(Token.Type.ESCAPED_BEGIN_PARAMETER, "\\{"), - tokenizeCharacter(Token.Type.BEGIN_PARAMETER, '{'), + tokenizeString(BEGIN_PARAMETER_ESCAPED, "\\{"), + tokenizeCharacter(BEGIN_PARAMETER, '{'), - tokenizeString(Token.Type.ESCAPED_END_PARAMETER, "\\}"), - tokenizeCharacter(Token.Type.END_PARAMETER, '}'), + tokenizeString(END_PARAMETER_ESCAPED, "\\}"), + tokenizeCharacter(END_PARAMETER, '}'), - tokenizeString(Token.Type.ESCAPED_ALTERNATION, "\\/"), - tokenizeCharacter(Token.Type.ALTERNATION, '/'), + tokenizeString(ALTERNATION_ESCAPED, "\\/"), + tokenizeCharacter(ALTERNATION, '/'), - tokenizeString(Token.Type.ESCAPED_ESCAPE, "\\\\"), - tokenizeString(Token.Type.ESCAPE, "\\"), + tokenizeString(ESCAPE_ESCAPED, "\\\\"), + tokenizeString(ESCAPE, "\\"), - tokenizePattern(Token.Type.TEXT, Pattern.compile("[^(){}\\\\/\\s]+")) + tokenizePattern(TEXT, Pattern.compile("[^(){}\\\\/\\s]+")) ); List tokenize(String expression) { @@ -131,28 +133,21 @@ public String toString() { } enum Type { - - ESCAPED_WHITE_SPACE, + // In order of precedence + WHITE_SPACE_ESCAPED, WHITE_SPACE, - - ESCAPED_BEGIN_OPTIONAL, + BEGIN_OPTIONAL_ESCAPED, BEGIN_OPTIONAL, - - ESCAPED_END_OPTIONAL, + END_OPTIONAL_ESCAPED, END_OPTIONAL, - - ESCAPED_BEGIN_PARAMETER, + BEGIN_PARAMETER_ESCAPED, BEGIN_PARAMETER, - - ESCAPED_END_PARAMETER, + END_PARAMETER_ESCAPED, END_PARAMETER, - - ESCAPED_ALTERNATION, + ALTERNATION_ESCAPED, ALTERNATION, - - ESCAPED_ESCAPE, + ESCAPE_ESCAPED, ESCAPE, - TEXT; } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 75e0af5a14..65bd2632db 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -9,12 +9,12 @@ import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_ALTERNATION; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_END_OPTIONAL; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_END_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPED_WHITE_SPACE; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION_ESCAPED; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.TEXT; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; import static org.hamcrest.MatcherAssert.assertThat; @@ -54,9 +54,9 @@ void optional() { @Test void escapedOptional() { assertThat(tokenizer.tokenize("\\(blind\\)"), contains( - new Token("\\(", ESCAPED_BEGIN_OPTIONAL), + new Token("\\(", BEGIN_OPTIONAL_ESCAPED), new Token("blind", TEXT), - new Token("\\)", ESCAPED_END_OPTIONAL) + new Token("\\)", END_OPTIONAL_ESCAPED) )); } @@ -85,9 +85,9 @@ void parameter() { @Test void EscapedParameter() { assertThat(tokenizer.tokenize("\\{string\\}"), contains( - new Token("\\{", ESCAPED_BEGIN_PARAMETER), + new Token("\\{", BEGIN_PARAMETER_ESCAPED), new Token("string", TEXT), - new Token("\\}", ESCAPED_END_PARAMETER) + new Token("\\}", END_PARAMETER_ESCAPED) )); } @@ -118,11 +118,11 @@ void alternation() { void escapedAlternation() { assertThat(tokenizer.tokenize("blind\\ and\\ famished\\/cripple"), contains( new Token("blind", TEXT), - new Token("\\ ", ESCAPED_WHITE_SPACE), + new Token("\\ ", WHITE_SPACE_ESCAPED), new Token("and", TEXT), - new Token("\\ ", ESCAPED_WHITE_SPACE), + new Token("\\ ", WHITE_SPACE_ESCAPED), new Token("famished", TEXT), - new Token("\\/", ESCAPED_ALTERNATION), + new Token("\\/", ALTERNATION_ESCAPED), new Token("cripple", TEXT) )); } From 84e92b50a04621e475a5d9f2a3a033ea1444f916 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 20:05:43 +0100 Subject: [PATCH 039/183] Document tokens --- cucumber-expressions/README.md | 9 +++++++-- .../cucumberexpressions/CucumberExpressionTokenizer.java | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 1e88a485e1..24039715a9 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -11,11 +11,16 @@ alternative: = optional | parameter | text optional := '(' + option* + ')' option := parameter | text parameter := '{' + text* + '}' -text := . +text := token + +token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | '\{' | '{' | '\}' | '}' | '\/' | '/' | '\\' | '\' | .+ ``` -Note: While `parameter` is allowed to appear as part of `alternative` and +Note: + * While `parameter` is allowed to appear as part of `alternative` and `option` in the AST, such an AST is not a valid a Cucumber Expression. + * All escaped tokens (tokens starting with a backslash) are rewritten to their + unescaped equivalent after parsing. ## Acknowledgements diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 92108bbf2a..96dc7e490b 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -37,6 +37,7 @@ private interface Tokenize { tokenizeString(ESCAPE_ESCAPED, "\\\\"), tokenizeString(ESCAPE, "\\"), + // Should be `.+` but this creates a nicer parse tree. tokenizePattern(TEXT, Pattern.compile("[^(){}\\\\/\\s]+")) ); From fbb282892b396865094e39d30218d8628cb5f5b6 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 20:06:30 +0100 Subject: [PATCH 040/183] Document tokens --- cucumber-expressions/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 24039715a9..edcb30a097 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -13,7 +13,8 @@ option := parameter | text parameter := '{' + text* + '}' text := token -token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | '\{' | '{' | '\}' | '}' | '\/' | '/' | '\\' | '\' | .+ +token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | '\{' | '{' | + '\}' | '}' | '\/' | '/' | '\\' | '\' | .+ ``` Note: From 41cd09264273f2d3318a3ad8a4182877dcac2c83 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 20:08:41 +0100 Subject: [PATCH 041/183] Document tokens --- cucumber-expressions/README.md | 2 +- .../cucumberexpressions/CucumberExpressionTokenizer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index edcb30a097..ca998100ee 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -14,7 +14,7 @@ parameter := '{' + text* + '}' text := token token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | '\{' | '{' | - '\}' | '}' | '\/' | '/' | '\\' | '\' | .+ + '\}' | '}' | '\/' | '/' | '\\' | '\' | . ``` Note: diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 96dc7e490b..65ff2842af 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -37,7 +37,7 @@ private interface Tokenize { tokenizeString(ESCAPE_ESCAPED, "\\\\"), tokenizeString(ESCAPE, "\\"), - // Should be `.+` but this creates a nicer parse tree. + // Should be `.` but this creates a nicer parse tree. tokenizePattern(TEXT, Pattern.compile("[^(){}\\\\/\\s]+")) ); From a903c5fcb8dfc78d7d284459109f24a3c4a16f71 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 20:10:48 +0100 Subject: [PATCH 042/183] Document tokens --- cucumber-expressions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index ca998100ee..2028611691 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -5,7 +5,7 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ``` cucumber-expression := ( alternation | optional | parameter | text )* -alternation := (?:boundry) + alternative* + ( '/' + alternative* )+ + (?:boundry) +alternation := (?<=boundry) + alternative* + ( '/' + alternative* )+ + (?=boundry) boundry := whitespace | ^ | $ alternative: = optional | parameter | text optional := '(' + option* + ')' From f6538aa728f154df04e971de3cde5dfa3381dc5b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 1 Nov 2019 20:11:36 +0100 Subject: [PATCH 043/183] Document tokens --- cucumber-expressions/README.md | 4 ++-- .../cucumberexpressions/CucumberExpressionParser.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 2028611691..c5b49f483c 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -5,8 +5,8 @@ See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for ``` cucumber-expression := ( alternation | optional | parameter | text )* -alternation := (?<=boundry) + alternative* + ( '/' + alternative* )+ + (?=boundry) -boundry := whitespace | ^ | $ +alternation := (?<=boundary) + alternative* + ( '/' + alternative* )+ + (?=boundary) +boundary := whitespace | ^ | $ alternative: = optional | parameter | text optional := '(' + option* + ')' option := parameter | text diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 029dd50044..34a6f16496 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -139,8 +139,8 @@ private static Parse parseBetween( ); /* - * alternation := (?:boundry) + alternative* + ( '/' + alternative* )+ + (?:boundry) - * boundry := whitespace | ^ | $ + * alternation := (?<=boundary) + alternative* + ( '/' + alternative* )+ + (?=boundary) + * boundary := whitespace | ^ | $ * alternative: = optional | parameter | text */ private static final Parse alternationParser = (ast, expression, current) -> { From 1a701d0a589fbaee7b00988ae7f594649529d9e5 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 2 Nov 2019 11:58:36 +0100 Subject: [PATCH 044/183] Clean up --- .../CucumberExpression.java | 5 +- .../CucumberExpressionParser.java | 10 ++-- .../CucumberExpressionParserTest.java | 53 +++++++++++-------- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index ff82a98e64..a2c38cca9f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -3,6 +3,7 @@ import io.cucumber.cucumberexpressions.CucumberExpressionParser.Node; import io.cucumber.cucumberexpressions.CucumberExpressionParser.Parameter; import io.cucumber.cucumberexpressions.CucumberExpressionParser.Text; +import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; import org.apiguardian.api.API; import java.lang.reflect.Type; @@ -30,8 +31,10 @@ public final class CucumberExpression implements Expression { this.source = expression; this.parameterTypeRegistry = parameterTypeRegistry; + CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + List tokens = tokenizer.tokenize(expression); CucumberExpressionParser parser = new CucumberExpressionParser(); - List ast = parser.parse(expression); + List ast = parser.parse(tokens); String pattern = ast.stream() .map(this::rewriteToRegex) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 34a6f16496..7b2b7cbb98 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -163,6 +163,9 @@ private static Parse parseBetween( return consumed; }; + // For readability + private static final Token.Type endOfLine = null; + private static final List cucumberExpressionParsers = asList( alternationParser, optionalParser, @@ -170,15 +173,12 @@ private static Parse parseBetween( textParser ); - private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); - /* * cucumber-expression := ( alternation | optional | parameter | text )* */ - List parse(String expression) { - List tokens = tokenizer.tokenize(expression); + List parse(List tokens) { List ast = new ArrayList<>(); - parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, null); + parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, endOfLine); return ast; } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 6f34ea981d..b9d10bd733 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -5,27 +5,31 @@ import io.cucumber.cucumberexpressions.CucumberExpressionParser.Optional; import io.cucumber.cucumberexpressions.CucumberExpressionParser.Parameter; import io.cucumber.cucumberexpressions.CucumberExpressionParser.Text; +import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; import org.junit.jupiter.api.Test; +import java.util.List; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; class CucumberExpressionParserTest { - private CucumberExpressionParser parser = new CucumberExpressionParser(); + private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + private final CucumberExpressionParser parser = new CucumberExpressionParser(); @Test void emptyString() { - assertThat(parser.parse(""), empty()); + assertThat(astOf(""), empty()); } @Test void phrase() { - assertThat(parser.parse("three blind mice"), contains( + assertThat(astOf("three blind mice"), contains( node("three", Text.class), node(" ", Text.class), node("blind", Text.class), @@ -36,28 +40,28 @@ void phrase() { @Test void optional() { - assertThat(parser.parse("(blind)"), contains( + assertThat(astOf("(blind)"), contains( node("blind", Optional.class) )); } @Test void parameter() { - assertThat(parser.parse("{string}"), contains( + assertThat(astOf("{string}"), contains( node("string", Parameter.class) )); } @Test void anonymousParameter() { - assertThat(parser.parse("{}"), contains( + assertThat(astOf("{}"), contains( node("", Parameter.class) )); } @Test void optionalPhrase() { - assertThat(parser.parse("three (blind) mice"), contains( + assertThat(astOf("three (blind) mice"), contains( node("three", Text.class), node(" ", Text.class), node("blind", Optional.class), @@ -68,35 +72,35 @@ void optionalPhrase() { @Test void slash() { - assertThat(parser.parse("\\"), contains( + assertThat(astOf("\\"), contains( node("\\", Text.class) )); } @Test void brace() { - assertThat(parser.parse("{"), contains( + assertThat(astOf("{"), contains( node("{", Text.class) )); } @Test void openingParenthesis() { - assertThat(parser.parse("("), contains( + assertThat(astOf("("), contains( node("(", Text.class) )); } @Test void escapedOpeningParenthesis() { - assertThat(parser.parse("\\("), contains( + assertThat(astOf("\\("), contains( node("(", Text.class) )); } @Test void escapedOptional() { - assertThat(parser.parse("\\(blind)"), contains( + assertThat(astOf("\\(blind)"), contains( node("(", Text.class), node("blind", Text.class), node(")", Text.class) @@ -105,7 +109,7 @@ void escapedOptional() { @Test void escapedOptionalPhrase() { - assertThat(parser.parse("three \\(blind) mice"), contains( + assertThat(astOf("three \\(blind) mice"), contains( node("three", Text.class), node(" ", Text.class), node("(", Text.class), @@ -118,7 +122,7 @@ void escapedOptionalPhrase() { @Test void escapedOptionalFollowedByOptional() { - assertThat(parser.parse("three \\((very) blind) mice"), contains( + assertThat(astOf("three \\((very) blind) mice"), contains( node("three", Text.class), node(" ", Text.class), node("(", Text.class), @@ -133,7 +137,7 @@ void escapedOptionalFollowedByOptional() { @Test void optionalContainingEscapedOptional() { - assertThat(parser.parse("three ((very\\) blind) mice"), contains( + assertThat(astOf("three ((very\\) blind) mice"), contains( node("three", Text.class), node(" ", Text.class), node("(very) blind", Optional.class), @@ -145,14 +149,14 @@ void optionalContainingEscapedOptional() { @Test void alternation() { - assertThat(parser.parse("mice/rats"), contains( + assertThat(astOf("mice/rats"), contains( node("mice - rats", Alternation.class) )); } @Test void escapedAlternation() { - assertThat(parser.parse("mice\\/rats"), contains( + assertThat(astOf("mice\\/rats"), contains( node("mice", Text.class), node("/", Text.class), node("rats", Text.class) @@ -162,7 +166,7 @@ void escapedAlternation() { @Test void alternationPhrase() { - assertThat(parser.parse("three hungry/blind mice"), contains( + assertThat(astOf("three hungry/blind mice"), contains( node("three", Text.class), node(" ", Text.class), node("hungry - blind", Alternation.class), @@ -173,14 +177,14 @@ void alternationPhrase() { @Test void alternationWithWhiteSpace() { - assertThat(parser.parse("\\ three\\ hungry/blind\\ mice\\ "), contains( + assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), contains( node(" three hungry - blind mice ", Alternation.class) )); } @Test void alternationWithUnusedEndOptional() { - assertThat(parser.parse("three )blind\\ mice/rats"), contains( + assertThat(astOf("three )blind\\ mice/rats"), contains( node("three", Text.class), node(" ", Text.class), node(")blind mice - rats", Alternation.class) @@ -189,7 +193,7 @@ void alternationWithUnusedEndOptional() { @Test void alternationWithUnusedStartOptional() { - assertThat(parser.parse("three blind\\ mice/rats("), contains( + assertThat(astOf("three blind\\ mice/rats("), contains( node("three", Text.class), node(" ", Text.class), node("blind mice - rats(", Alternation.class) @@ -198,13 +202,18 @@ void alternationWithUnusedStartOptional() { @Test void alternationFollowedByOptional() { - assertThat(parser.parse("three blind\\ rat/cat(s)"), contains( + assertThat(astOf("three blind\\ rat/cat(s)"), contains( node("three", Text.class), node(" ", Text.class), node("blind rat - cats", Alternation.class) )); } + private List astOf(String s) { + List tokens = tokenizer.tokenize(s); + return parser.parse(tokens); + } + private static Matcher node(String expectedExpression, Class type) { return new TypeSafeDiagnosingMatcher() { @Override From 04367eb1cd2ae19965e972b1d3c46cc14a01f39d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 2 Nov 2019 12:28:30 +0100 Subject: [PATCH 045/183] Use proper SOL/EOL tokens --- .../CucumberExpressionParser.java | 58 ++++++++++++------- .../CucumberExpressionTokenizer.java | 2 + 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 7b2b7cbb98..65c101d008 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -15,12 +15,14 @@ import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OF_LINE; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER_ESCAPED; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPE; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPE_ESCAPED; +import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.START_OF_LINE; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE_ESCAPED; import static java.util.Arrays.asList; @@ -92,8 +94,7 @@ private static Parse parseBetween( List parsers, Function, Node> create) { return (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type != beginToken) { + if (!lookingAt(expression, current, beginToken)) { return 0; } List subAst = new ArrayList<>(); @@ -102,11 +103,10 @@ private static Parse parseBetween( subCurrent += consumed; // endToken not found - if (subCurrent == expression.size()) { + if (lookingAt(expression, subCurrent, END_OF_LINE)) { return 0; } - Token end = expression.get(subCurrent); - if (end.type != endToken) { + if (!lookingAt(expression, subCurrent, endToken)) { return 0; } ast.add(create.apply(subAst)); @@ -123,8 +123,7 @@ private static Parse parseBetween( }; private static Parse alternationSeparatorParser = (ast, expression, current) -> { - Token currentToken = expression.get(current); - if (currentToken.type != ALTERNATION) { + if (!lookingAt(expression, current, ALTERNATION)) { return 0; } ast.add(NODE_ALTERNATION); @@ -144,15 +143,14 @@ private static Parse parseBetween( * alternative: = optional | parameter | text */ private static final Parse alternationParser = (ast, expression, current) -> { - if (current > 0) { - Token previousToken = expression.get(current - 1); - if (previousToken.type != WHITE_SPACE) { - return 0; - } + int previous = current - 1; + if (!lookingAt(expression, previous, START_OF_LINE) + && !lookingAt(expression, previous, WHITE_SPACE)) { + return 0; } List subAst = new ArrayList<>(); - int consumed = parseTokensUntil(alternationParsers, subAst, expression, current, WHITE_SPACE); + int consumed = parseTokensUntil(alternationParsers, subAst, expression, current, WHITE_SPACE, END_OF_LINE); if (!subAst.contains(NODE_ALTERNATION)) { return 0; } @@ -163,9 +161,6 @@ private static Parse parseBetween( return consumed; }; - // For readability - private static final Token.Type endOfLine = null; - private static final List cucumberExpressionParsers = asList( alternationParser, optionalParser, @@ -178,7 +173,7 @@ private static Parse parseBetween( */ List parse(List tokens) { List ast = new ArrayList<>(); - parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, endOfLine); + parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, END_OF_LINE); return ast; } @@ -186,13 +181,13 @@ private static int parseTokensUntil(List parsers, List ast, List expression, int startAt, - Token.Type endToken) { + Token.Type... endTokens) { int current = startAt; while (current < expression.size()) { - Token currentToken = expression.get(current); - if (currentToken.type == endToken) { + if (lookingAt(expression, current, endTokens)) { break; } + int consumed = parseToken(parsers, ast, expression, current); if (consumed == 0) { // If configured correctly this will never happen @@ -219,6 +214,29 @@ private static int parseToken(List parsers, return current - startAt; } + private static boolean lookingAt(List expression, int current, Token.Type... endTokens) { + for (Token.Type endToken : endTokens) { + if (lookingAt(expression, current, endToken)) { + return true; + } + } + return false; + } + + private static boolean lookingAt(List expression, int at, Token.Type token) { + if (at < 0 || at >= expression.size()) { + if (token == START_OF_LINE) { + return at < 0; + } + if (token == END_OF_LINE) { + return at >= expression.size(); + } + return false; + } + Token currentToken = expression.get(at); + return currentToken.type == token; + } + private static Function rewrite(Map> rewriteRules) { return token -> rewriteRules.computeIfAbsent(token.type, type -> identity()).apply(token); } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 65ff2842af..f8e63314c8 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -134,6 +134,8 @@ public String toString() { } enum Type { + START_OF_LINE, + END_OF_LINE, // In order of precedence WHITE_SPACE_ESCAPED, WHITE_SPACE, From 0931afd9b1624437dad978a3878be069a6a2826d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 2 Nov 2019 12:52:46 +0100 Subject: [PATCH 046/183] Naming --- .../CucumberExpressionParser.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 65c101d008..4444d67369 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -115,23 +115,23 @@ private static Parse parseBetween( }; } - private static final Node NODE_ALTERNATION = new Node() { + private static final Node ALTERNATIVE_SEPARATOR = new Node() { // Marker. This way we don't need to model the // the tail end of alternation in the AST: // // alternation := alternative* + ( '/' + alternative* )+ }; - private static Parse alternationSeparatorParser = (ast, expression, current) -> { + private static Parse alternativeSeparator = (ast, expression, current) -> { if (!lookingAt(expression, current, ALTERNATION)) { return 0; } - ast.add(NODE_ALTERNATION); + ast.add(ALTERNATIVE_SEPARATOR); return 1; }; - private static final List alternationParsers = asList( - alternationSeparatorParser, + private static final List alternativeParsers = asList( + alternativeSeparator, optionalParser, parameterParser, textParser @@ -150,8 +150,8 @@ private static Parse parseBetween( } List subAst = new ArrayList<>(); - int consumed = parseTokensUntil(alternationParsers, subAst, expression, current, WHITE_SPACE, END_OF_LINE); - if (!subAst.contains(NODE_ALTERNATION)) { + int consumed = parseTokensUntil(alternativeParsers, subAst, expression, current, WHITE_SPACE, END_OF_LINE); + if (!subAst.contains(ALTERNATIVE_SEPARATOR)) { return 0; } @@ -246,7 +246,7 @@ private static List> splitOnAlternation(List tokens) { List alternative = new ArrayList<>(); alternatives.add(alternative); for (T token : tokens) { - if (NODE_ALTERNATION.equals(token)) { + if (ALTERNATIVE_SEPARATOR.equals(token)) { alternative = new ArrayList<>(); alternatives.add(alternative); } else { From 8d5f12a0d30f12b3e26ac3d279b72ae63dced902 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 7 Nov 2019 12:22:05 +0100 Subject: [PATCH 047/183] Clean up --- .../cucumberexpressions/CucumberExpressionParser.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 4444d67369..8f2aa1ce91 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -171,9 +171,9 @@ private static Parse parseBetween( /* * cucumber-expression := ( alternation | optional | parameter | text )* */ - List parse(List tokens) { + List parse(List expression) { List ast = new ArrayList<>(); - parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, END_OF_LINE); + parseTokensUntil(cucumberExpressionParsers, ast, expression, 0, END_OF_LINE); return ast; } @@ -286,7 +286,7 @@ static final class Optional extends Node { this.optional = optional; } - public List getOptional() { + List getOptional() { return optional; } From 5f92932a2b2fc117b407f2331d61d663a3f079a8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 7 Nov 2019 13:36:34 +0100 Subject: [PATCH 048/183] Make AST explicit --- .../cucumber/cucumberexpressions/AstNode.java | 164 ++++++++++++++ .../CucumberExpression.java | 44 ++-- .../CucumberExpressionParser.java | 210 +++++------------- .../CucumberExpressionTokenizer.java | 80 ++----- .../CucumberExpressionParserTest.java | 23 +- .../CucumberExpressionTokenizerTest.java | 28 +-- 6 files changed, 291 insertions(+), 258 deletions(-) create mode 100644 cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java new file mode 100644 index 0000000000..af2b517fa1 --- /dev/null +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java @@ -0,0 +1,164 @@ +package io.cucumber.cucumberexpressions; + +import java.util.List; +import java.util.Objects; + +import static java.util.stream.Collectors.joining; + +abstract class AstNode { + + static final class Expression extends AstNode { + + private final List nodes; + + Expression(List nodes) { + this.nodes = nodes; + } + + List getNodes() { + return nodes; + } + } + + static final class Text extends AstNode { + + private final Token token; + + Text(Token token) { + this.token = token; + } + + @Override + public String toString() { + return getText(); + } + + String getText() { + return token.text; + } + } + + static final class Optional extends AstNode { + + private final List optional; + + Optional(List optional) { + this.optional = optional; + } + + List getOptional() { + return optional; + } + + @Override + public String toString() { + return optional.stream() + .map(Object::toString) + .collect(joining()); + } + } + + static final class Parameter extends AstNode { + + private final List nodes; + + Parameter(List nodes) { + this.nodes = nodes; + } + + @Override + public String toString() { + return getParameterName(); + } + + String getParameterName() { + return nodes.stream() + .map(Text.class::cast) + .map(Text::getText) + .collect(joining()); + } + } + + static final class Alternation extends AstNode { + + private final List> alternatives; + + Alternation(List> alternatives) { + this.alternatives = alternatives; + } + + List> getAlternatives() { + return alternatives; + } + + @Override + public String toString() { + return getAlternatives().stream() + .map(nodes -> nodes.stream() + .map(Objects::toString) + .collect(joining())) + .collect(joining(" - ")); + } + } + + static final class Token extends AstNode { + + static final Token BEGIN_PARAMETER = new Token("{", Type.BEGIN_PARAMETER); + static final Token END_PARAMETER = new Token("}", Type.END_PARAMETER); + static final Token BEGIN_OPTIONAL = new Token("(", Type.BEGIN_OPTIONAL); + static final Token END_OPTIONAL = new Token(")", Type.END_OPTIONAL); + static final Token ESCAPE = new Token("\\", Type.ESCAPE); + static final Token ALTERNATION = new Token("/", Type.ALTERNATION); + + final String text; + final Type type; + + Token(String text, Type type) { + this.text = text; + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Token token = (Token) o; + return text.equals(token.text) && + type == token.type; + } + + @Override + public int hashCode() { + return Objects.hash(text, type); + } + + @Override + public String toString() { + return "Token{" + + "text='" + text + '\'' + + ", type=" + type + + '}'; + } + + enum Type { + START_OF_LINE, + END_OF_LINE, + // In order of precedence + WHITE_SPACE_ESCAPED, + WHITE_SPACE, + BEGIN_OPTIONAL_ESCAPED, + BEGIN_OPTIONAL, + END_OPTIONAL_ESCAPED, + END_OPTIONAL, + BEGIN_PARAMETER_ESCAPED, + BEGIN_PARAMETER, + END_PARAMETER_ESCAPED, + END_PARAMETER, + ALTERNATION_ESCAPED, + ALTERNATION, + ESCAPE_ESCAPED, + ESCAPE, + TEXT; + } + } +} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index a2c38cca9f..153fd9cc70 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -1,9 +1,9 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.CucumberExpressionParser.Node; -import io.cucumber.cucumberexpressions.CucumberExpressionParser.Parameter; -import io.cucumber.cucumberexpressions.CucumberExpressionParser.Text; -import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; +import io.cucumber.cucumberexpressions.AstNode.Alternation; +import io.cucumber.cucumberexpressions.AstNode.Optional; +import io.cucumber.cucumberexpressions.AstNode.Parameter; +import io.cucumber.cucumberexpressions.AstNode.Text; import org.apiguardian.api.API; import java.lang.reflect.Type; @@ -31,22 +31,15 @@ public final class CucumberExpression implements Expression { this.source = expression; this.parameterTypeRegistry = parameterTypeRegistry; - CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); - List tokens = tokenizer.tokenize(expression); CucumberExpressionParser parser = new CucumberExpressionParser(); - List ast = parser.parse(tokens); - - String pattern = ast.stream() - .map(this::rewriteToRegex) - .collect(joining("", "^", "$")); - - + AstNode.Expression ast = parser.parse(expression); + String pattern = rewriteToRegex(ast); treeRegexp = new TreeRegexp(pattern); } - private String rewriteToRegex(Node node) { - if (node instanceof CucumberExpressionParser.Optional) { - CucumberExpressionParser.Optional optional = (CucumberExpressionParser.Optional) node; + private String rewriteToRegex(AstNode node) { + if (node instanceof Optional) { + Optional optional = (Optional) node; assertNoParameters(optional.getOptional(), PARAMETER_TYPES_CANNOT_BE_OPTIONAL); assertNotEmpty(optional.getOptional(), OPTIONAL_MAY_NOT_BE_EMPTY); return optional.getOptional().stream() @@ -54,8 +47,8 @@ private String rewriteToRegex(Node node) { .collect(joining("", "(?:", ")?")); } - if (node instanceof CucumberExpressionParser.Alternation) { - CucumberExpressionParser.Alternation alternation = (CucumberExpressionParser.Alternation) node; + if (node instanceof Alternation) { + Alternation alternation = (Alternation) node; validateAlternation(alternation); return alternation.getAlternatives() .stream() @@ -87,10 +80,17 @@ private String rewriteToRegex(Node node) { return escapeRegex(text.getText()); } + if (node instanceof AstNode.Expression) { + AstNode.Expression xpo = (AstNode.Expression) node; + return xpo.getNodes().stream() + .map(this::rewriteToRegex) + .collect(joining("", "^", "$")); + } + throw new IllegalArgumentException(node.getClass().getName()); } - private void assertNotEmpty(List nodes, String message) { + private void assertNotEmpty(List nodes, String message) { boolean hasTextNode = nodes .stream() .anyMatch(Text.class::isInstance); @@ -99,9 +99,9 @@ private void assertNotEmpty(List nodes, String message) { } } - private void validateAlternation(CucumberExpressionParser.Alternation alternation) { + private void validateAlternation(Alternation alternation) { // Make sure the alternative parts aren't empty and don't contain parameter types - for (List alternative : alternation.getAlternatives()) { + for (List alternative : alternation.getAlternatives()) { if (alternative.isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } @@ -110,7 +110,7 @@ private void validateAlternation(CucumberExpressionParser.Alternation alternatio } } - private void assertNoParameters(List alternative, String parameterTypesCannotBeAlternative) { + private void assertNoParameters(List alternative, String parameterTypesCannotBeAlternative) { boolean hasParameter = alternative.stream() .anyMatch(Parameter.class::isInstance); if (hasParameter) { diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 8f2aa1ce91..633d183ae5 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -1,72 +1,65 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; +import io.cucumber.cucumberexpressions.AstNode.Alternation; +import io.cucumber.cucumberexpressions.AstNode.Expression; +import io.cucumber.cucumberexpressions.AstNode.Optional; +import io.cucumber.cucumberexpressions.AstNode.Parameter; +import io.cucumber.cucumberexpressions.AstNode.Text; +import io.cucumber.cucumberexpressions.AstNode.Token; +import io.cucumber.cucumberexpressions.AstNode.Token.Type; import java.util.ArrayList; -import java.util.EnumMap; import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.function.Function; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OF_LINE; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPE; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ESCAPE_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.START_OF_LINE; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OF_LINE; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.START_OF_LINE; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.joining; final class CucumberExpressionParser { - private static final Token TOKEN_BEGIN_PARAMETER = new Token("{", BEGIN_PARAMETER); - private static final Token TOKEN_END_PARAMETER = new Token("}", END_PARAMETER); - private static final Token TOKEN_BEGIN_OPTIONAL = new Token("(", BEGIN_OPTIONAL); - private static final Token TOKEN_END_OPTIONAL = new Token(")", END_OPTIONAL); - private static final Token TOKEN_ESCAPE = new Token("\\", ESCAPE); - private static final Token TOKEN_ALTERNATION = new Token("/", ALTERNATION); - - private static final Function escapeText; - - static { - Map> escapesInText = new EnumMap<>(Token.Type.class); - escapesInText.put(ESCAPE_ESCAPED, token -> TOKEN_ESCAPE); - escapesInText.put(WHITE_SPACE_ESCAPED, token -> new Token(token.text.substring(1), WHITE_SPACE)); - escapesInText.put(BEGIN_OPTIONAL_ESCAPED, token -> TOKEN_BEGIN_OPTIONAL); - escapesInText.put(END_OPTIONAL_ESCAPED, token -> TOKEN_END_OPTIONAL); - escapesInText.put(ALTERNATION_ESCAPED, token -> TOKEN_ALTERNATION); - escapesInText.put(BEGIN_PARAMETER_ESCAPED, token -> TOKEN_BEGIN_PARAMETER); - escapesInText.put(END_PARAMETER_ESCAPED, token -> TOKEN_END_PARAMETER); - escapeText = rewrite(escapesInText); - } - private interface Parse { - int parse(List ast, List expression, int current); + int parse(List ast, List expression, int current); } /* - * text := . + * text := token */ private static final Parse textParser = (ast, expression, current) -> { Token currentToken = expression.get(current); - Token unescaped = escapeText.apply(currentToken); + Token unescaped = unEscape(currentToken); ast.add(new Text(unescaped)); return 1; }; + private static Token unEscape(Token currentToken) { + switch (currentToken.type) { + case WHITE_SPACE_ESCAPED: + return new Token(currentToken.text.substring(1), Type.WHITE_SPACE); + case BEGIN_OPTIONAL_ESCAPED: + return Token.BEGIN_OPTIONAL; + case END_OPTIONAL_ESCAPED: + return Token.END_OPTIONAL; + case BEGIN_PARAMETER_ESCAPED: + return Token.BEGIN_PARAMETER; + case END_PARAMETER_ESCAPED: + return Token.END_PARAMETER; + case ALTERNATION_ESCAPED: + return Token.ALTERNATION; + case ESCAPE_ESCAPED: + return Token.ESCAPE; + default: + return currentToken; + } + } + /* * parameter := '{' + text* + '}' */ @@ -89,15 +82,15 @@ private interface Parse { ); private static Parse parseBetween( - Token.Type beginToken, - Token.Type endToken, + Type beginToken, + Type endToken, List parsers, - Function, Node> create) { + Function, AstNode> create) { return (ast, expression, current) -> { if (!lookingAt(expression, current, beginToken)) { return 0; } - List subAst = new ArrayList<>(); + List subAst = new ArrayList<>(); int subCurrent = current + 1; int consumed = parseTokensUntil(parsers, subAst, expression, subCurrent, endToken); subCurrent += consumed; @@ -115,7 +108,7 @@ private static Parse parseBetween( }; } - private static final Node ALTERNATIVE_SEPARATOR = new Node() { + private static final AstNode ALTERNATIVE_SEPARATOR = new AstNode() { // Marker. This way we don't need to model the // the tail end of alternation in the AST: // @@ -149,13 +142,13 @@ private static Parse parseBetween( return 0; } - List subAst = new ArrayList<>(); + List subAst = new ArrayList<>(); int consumed = parseTokensUntil(alternativeParsers, subAst, expression, current, WHITE_SPACE, END_OF_LINE); if (!subAst.contains(ALTERNATIVE_SEPARATOR)) { return 0; } - List> alternatives = splitOnAlternation(subAst); + List> alternatives = splitOnAlternation(subAst); ast.add(new Alternation(alternatives)); // Does not consume right hand boundary token return consumed; @@ -171,17 +164,19 @@ private static Parse parseBetween( /* * cucumber-expression := ( alternation | optional | parameter | text )* */ - List parse(List expression) { - List ast = new ArrayList<>(); - parseTokensUntil(cucumberExpressionParsers, ast, expression, 0, END_OF_LINE); - return ast; + Expression parse(String expression) { + CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + List tokens = tokenizer.tokenize(expression); + List ast = new ArrayList<>(); + parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, END_OF_LINE); + return new Expression(ast); } private static int parseTokensUntil(List parsers, - List ast, + List ast, List expression, int startAt, - Token.Type... endTokens) { + Type... endTokens) { int current = startAt; while (current < expression.size()) { if (lookingAt(expression, current, endTokens)) { @@ -200,7 +195,7 @@ private static int parseTokensUntil(List parsers, } private static int parseToken(List parsers, - List ast, + List ast, List expression, int startAt) { int current = startAt; @@ -214,8 +209,8 @@ private static int parseToken(List parsers, return current - startAt; } - private static boolean lookingAt(List expression, int current, Token.Type... endTokens) { - for (Token.Type endToken : endTokens) { + private static boolean lookingAt(List expression, int current, Type... endTokens) { + for (Type endToken : endTokens) { if (lookingAt(expression, current, endToken)) { return true; } @@ -223,7 +218,7 @@ private static boolean lookingAt(List expression, int current, Token.Type return false; } - private static boolean lookingAt(List expression, int at, Token.Type token) { + private static boolean lookingAt(List expression, int at, Type token) { if (at < 0 || at >= expression.size()) { if (token == START_OF_LINE) { return at < 0; @@ -237,10 +232,6 @@ private static boolean lookingAt(List expression, int at, Token.Type toke return currentToken.type == token; } - private static Function rewrite(Map> rewriteRules) { - return token -> rewriteRules.computeIfAbsent(token.type, type -> identity()).apply(token); - } - private static List> splitOnAlternation(List tokens) { List> alternatives = new ArrayList<>(); List alternative = new ArrayList<>(); @@ -256,89 +247,4 @@ private static List> splitOnAlternation(List tokens) { return alternatives; } - static abstract class Node { - - } - - static final class Text extends Node { - - private final Token token; - - Text(Token token) { - this.token = token; - } - - @Override - public String toString() { - return getText(); - } - - String getText() { - return token.text; - } - } - - static final class Optional extends Node { - - private final List optional; - - Optional(List optional) { - this.optional = optional; - } - - List getOptional() { - return optional; - } - - @Override - public String toString() { - return optional.stream() - .map(Object::toString) - .collect(joining()); - } - } - - static final class Parameter extends Node { - - private final List nodes; - - Parameter(List nodes) { - this.nodes = nodes; - } - - @Override - public String toString() { - return getParameterName(); - } - - String getParameterName() { - return nodes.stream() - .map(Text.class::cast) - .map(Text::getText) - .collect(joining()); - } - } - - static final class Alternation extends Node { - - private final List> alternatives; - - Alternation(List> alternatives) { - this.alternatives = alternatives; - } - - List> getAlternatives() { - return alternatives; - } - - @Override - public String toString() { - return getAlternatives().stream() - .map(nodes -> nodes.stream() - .map(Objects::toString) - .collect(joining())) - .collect(joining(" - ")); - } - } - } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index f8e63314c8..014cb23b43 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -1,15 +1,30 @@ package io.cucumber.cucumberexpressions; +import io.cucumber.cucumberexpressions.AstNode.Token; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.*; - -class CucumberExpressionTokenizer { +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ESCAPE; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ESCAPE_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.TEXT; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE_ESCAPED; + +final class CucumberExpressionTokenizer { private interface Tokenize { int tokenize(List tokens, String expression, int current); @@ -41,6 +56,10 @@ private interface Tokenize { tokenizePattern(TEXT, Pattern.compile("[^(){}\\\\/\\s]+")) ); + /* + * token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | + * '\{' | '{' | '\}' | '}' | '\/' | '/' | '\\' | '\' | . + */ List tokenize(String expression) { List tokens = new ArrayList<>(); int length = expression.length(); @@ -101,57 +120,4 @@ private static Tokenize tokenizePattern(Token.Type type, Pattern pattern) { } - static class Token { - final String text; - final Type type; - - Token(String text, Type type) { - this.text = text; - this.type = type; - } - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Token token = (Token) o; - return text.equals(token.text) && - type == token.type; - } - - @Override - public int hashCode() { - return Objects.hash(text, type); - } - - @Override - public String toString() { - return "Token{" + - "text='" + text + '\'' + - ", type=" + type + - '}'; - } - - enum Type { - START_OF_LINE, - END_OF_LINE, - // In order of precedence - WHITE_SPACE_ESCAPED, - WHITE_SPACE, - BEGIN_OPTIONAL_ESCAPED, - BEGIN_OPTIONAL, - END_OPTIONAL_ESCAPED, - END_OPTIONAL, - BEGIN_PARAMETER_ESCAPED, - BEGIN_PARAMETER, - END_PARAMETER_ESCAPED, - END_PARAMETER, - ALTERNATION_ESCAPED, - ALTERNATION, - ESCAPE_ESCAPED, - ESCAPE, - TEXT; - } - } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index b9d10bd733..2156d0a42c 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -1,11 +1,10 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.CucumberExpressionParser.Alternation; -import io.cucumber.cucumberexpressions.CucumberExpressionParser.Node; -import io.cucumber.cucumberexpressions.CucumberExpressionParser.Optional; -import io.cucumber.cucumberexpressions.CucumberExpressionParser.Parameter; -import io.cucumber.cucumberexpressions.CucumberExpressionParser.Text; -import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; +import io.cucumber.cucumberexpressions.AstNode.Alternation; +import io.cucumber.cucumberexpressions.AstNode.Optional; +import io.cucumber.cucumberexpressions.AstNode.Parameter; +import io.cucumber.cucumberexpressions.AstNode.Text; +import io.cucumber.cucumberexpressions.AstNode.Token; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -19,7 +18,6 @@ class CucumberExpressionParserTest { - private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); private final CucumberExpressionParser parser = new CucumberExpressionParser(); @Test @@ -209,13 +207,12 @@ void alternationFollowedByOptional() { )); } - private List astOf(String s) { - List tokens = tokenizer.tokenize(s); - return parser.parse(tokens); + private List astOf(String expression) { + return parser.parse(expression).getNodes(); } - private static Matcher node(String expectedExpression, Class type) { - return new TypeSafeDiagnosingMatcher() { + private static Matcher node(String expectedExpression, Class type) { + return new TypeSafeDiagnosingMatcher() { @Override public void describeTo(Description description) { description.appendText("("); @@ -226,7 +223,7 @@ public void describeTo(Description description) { } @Override - protected boolean matchesSafely(Node node, Description description) { + protected boolean matchesSafely(AstNode node, Description description) { description.appendText("("); String expression = node.toString(); description.appendValue(expression); diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 65bd2632db..a08f6d73e9 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -1,22 +1,22 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token; +import io.cucumber.cucumberexpressions.AstNode.Token; import org.junit.jupiter.api.Test; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.ALTERNATION_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.BEGIN_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.END_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE_ESCAPED; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.TEXT; -import static io.cucumber.cucumberexpressions.CucumberExpressionTokenizer.Token.Type.WHITE_SPACE; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.TEXT; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE; +import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE_ESCAPED; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; From 543d0302303c0bc46c5f7ec1c281b4394dbd0254 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 7 Nov 2019 16:04:39 +0100 Subject: [PATCH 049/183] Implement tokenizer in go --- cucumber-expressions/go/ast.go | 29 +++ .../go/cucumber_expression.go | 175 +----------------- .../go/cucumber_expression_generator.go | 14 +- .../go/cucumber_expression_regexp_test.go | 3 +- .../go/cucumber_expression_tokenizer.go | 85 +++++++++ .../go/cucumber_expression_tokenizer_test.go | 117 ++++++++++++ .../CucumberExpressionTokenizer.java | 27 ++- .../CucumberExpressionTokenizerTest.java | 12 +- 8 files changed, 259 insertions(+), 203 deletions(-) create mode 100644 cucumber-expressions/go/ast.go create mode 100644 cucumber-expressions/go/cucumber_expression_tokenizer.go create mode 100644 cucumber-expressions/go/cucumber_expression_tokenizer_test.go diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go new file mode 100644 index 0000000000..e056bae974 --- /dev/null +++ b/cucumber-expressions/go/ast.go @@ -0,0 +1,29 @@ +package cucumberexpressions + +type tokenType int + +const ( + startOfLine tokenType = iota + EndOfLine + // In order of precedence + whiteSpaceEscaped + whiteSpace + beginOptionalEscaped + beginOptional + endOptionalEscaped + endOptional + beginParameterEscaped + beginParameter + endParameterEscaped + endParameter + alternationEscaped + alternation + escapeEscaped + escape + text +) + +type token struct { + text string + tokenType tokenType +} diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 1280d239d1..bf62b055bd 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -1,10 +1,8 @@ package cucumberexpressions import ( - "fmt" "reflect" "regexp" - "strings" ) const alternativesMayNotBeEmpty = "Alternative may not be empty: %s" @@ -12,23 +10,6 @@ const parameterTypesCanNotBeAlternative = "Parameter types cannot be alternative const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" var escapeRegexp = regexp.MustCompile(`([\\^\[({$.|?*+})\]])`) -var parameterRegexp = regexp.MustCompile(`((?:\\){0,2}){([^}]*)}`) -var optionalRegexp = regexp.MustCompile(`((?:\\){0,2})\(([^)]+)\)`) -var alternativeNonWhitespaceTextRegexp = regexp.MustCompile(`([^\s/]*)((/[^\s/]*)+)`) - -type tokenType int - -const ( - text tokenType = 0 - optional tokenType = 1 - alternation tokenType = 2 - parameter tokenType = 3 -) - -type token struct { - text string - tokenType tokenType -} type CucumberExpression struct { source string @@ -39,25 +20,7 @@ type CucumberExpression struct { func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (Expression, error) { result := &CucumberExpression{source: expression, parameterTypeRegistry: parameterTypeRegistry} - - tokens := []token{{expression, text}} - - tokens, err := result.processOptional(tokens) - if err != nil { - return nil, err - } - - tokens, err = result.processAlternation(tokens) - if err != nil { - return nil, err - } - - tokens, err = result.processParameters(tokens, parameterTypeRegistry) - if err != nil { - return nil, err - } - - pattern := result.escapeRegexAndJoin(tokens, "^", "$") + pattern := "^$" result.treeRegexp = NewTreeRegexp(regexp.MustCompile(pattern)) return result, nil } @@ -94,142 +57,6 @@ func (c *CucumberExpression) Source() string { return c.source } -func (c *CucumberExpression) escapeRegexAndJoin(expression []token, prefix string, suffix string) string { - var builder strings.Builder - builder.WriteString(prefix) - for _, token := range expression { - if token.tokenType == text { - builder.WriteString(c.escapeRegex(token.text)) - } else { - builder.WriteString(token.text) - } - } - builder.WriteString(suffix) - return builder.String() -} - -func (c *CucumberExpression) escapeRegex(expression string) string { - return escapeRegexp.ReplaceAllString(expression, `\$1`) -} - -func (c *CucumberExpression) processOptional(expression []token) ([]token, error) { - var err error - result := splitTextTokens(expression, optionalRegexp, func(match []string) (token) { - optionalPart := match[2] - escapes := match[1] - // look for single-escaped parentheses - if len(escapes) == 1 { - return token{`(` + optionalPart + `)`, text} - } - if parameterRegexp.MatchString(optionalPart) { - err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeOptional, c.source)) - } - // either no or double escape - return token{escapes + "(?:" + optionalPart + ")?", optional} - }) - return result, err -} - -func (c *CucumberExpression) processAlternation(expression []token) ([]token, error) { - var err error - result := splitTextTokens(expression, alternativeNonWhitespaceTextRegexp, func(match []string) token { - // replace \/ with / - // replace / with | - replacement := strings.Replace(match[0], "/", "|", -1) - replacement = strings.Replace(replacement, `\|`, "/", -1) - - if !strings.Contains(replacement, "|") { - // All / were escaped - return token{replacement, text} - } - - // Make sure the alternative alternatives aren't empty and don't contain parameter types - alternatives := strings.Split(replacement, "|") - if len(alternatives) == 0 { - err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) - } - alternativeTexts := make([]string, len(alternatives)) - for i, alternative := range alternatives { - if len(alternative) == 0 { - err = NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) - } - if parameterRegexp.MatchString(alternative) { - err = NewCucumberExpressionError(fmt.Sprintf(parameterTypesCanNotBeAlternative, c.source)) - } - alternativeTexts[i] = c.escapeRegex(alternative) - } - alternativeText := strings.Join(alternativeTexts, "|") - return token{"(?:" + alternativeText + ")", alternation} - }) - return result, err -} - -func (c *CucumberExpression) processParameters(expression []token, parameterTypeRegistry *ParameterTypeRegistry) ([]token, error) { - var err error - result := splitTextTokens(expression, parameterRegexp, func(match []string) token { - typeName := match[2] - escapes := match[1] - // look for single-escaped parentheses - if len(escapes) == 1 { - return token{"{" + typeName + "}", text} - } - err = CheckParameterTypeName(typeName) - if err != nil { - return token{typeName, parameter} - } - parameterType := parameterTypeRegistry.LookupByTypeName(typeName) - if parameterType == nil { - err = NewUndefinedParameterTypeError(typeName) - return token{typeName, parameter} - } - c.parameterTypes = append(c.parameterTypes, parameterType) - // either no or double escape - return token{escapes + buildCaptureRegexp(parameterType.regexps), parameter} - }) - return result, err -} - -func splitTextTokens(tokens []token, regexp *regexp.Regexp, processor func([]string) token) []token { - // Guesstimate: When a match is found this splitTextTokens will at a minimum - // create 2 additional tokens. Adding 8 additional capacity allows for a few - // more - newTokens := make([]token, 0, len(tokens)+8) - for _, t := range tokens { - if t.tokenType != text { - newTokens = append(newTokens, t) - continue - } - expression := t.text - locations := regexp.FindAllStringIndex(expression, -1) - groups := regexp.FindAllStringSubmatch(expression, -1) - previousEnd := 0 - for i := 0; i < len(locations); i++ { - start := locations[i][0] - end := locations[i][1] - prefix := expression[previousEnd:start] - newTokens = append(newTokens, token{prefix, text}) - newTokens = append(newTokens, processor(groups[i])) - previousEnd = end - } - suffix := expression[previousEnd:] - newTokens = append(newTokens, token{suffix, text}) - } - return newTokens -} - -func buildCaptureRegexp(regexps []*regexp.Regexp) string { - if len(regexps) == 1 { - return "(" + regexps[0].String() + ")" - } - - captureGroups := make([]string, len(regexps)) - for i, r := range regexps { - captureGroups[i] = "(?:" + r.String() + ")" - } - - return "(" + strings.Join(captureGroups, "|") + ")" -} - func (r *CucumberExpression) objectMapperTransformer(typeHint reflect.Type) func(args ...*string) interface{} { return func(args ...*string) interface{} { i, err := r.parameterTypeRegistry.defaultTransformer.Transform(*args[0], typeHint) diff --git a/cucumber-expressions/go/cucumber_expression_generator.go b/cucumber-expressions/go/cucumber_expression_generator.go index cb899ba4dc..605dc125d4 100644 --- a/cucumber-expressions/go/cucumber_expression_generator.go +++ b/cucumber-expressions/go/cucumber_expression_generator.go @@ -16,6 +16,13 @@ func NewCucumberExpressionGenerator(parameterTypeRegistry *ParameterTypeRegistry } func (c *CucumberExpressionGenerator) GenerateExpressions(text string) []*GeneratedExpression { + escape := func(s string) string { + result := strings.Replace(s, "%", "%%", -1) + result = strings.Replace(result, `(`, `\(`, -1) + result = strings.Replace(result, `{`, `\{`, -1) + return strings.Replace(result, `/`, `\/`, -1) + } + parameterTypeCombinations := [][]*ParameterType{} parameterTypeMatchers := c.createParameterTypeMatchers(text) expressionTemplate := "" @@ -93,10 +100,3 @@ func (c *CucumberExpressionGenerator) createParameterTypeMatchers2(parameterType } return result } - -func escape(s string) string { - result := strings.Replace(s, "%", "%%", -1) - result = strings.Replace(result, `(`, `\(`, -1) - result = strings.Replace(result, `{`, `\{`, -1) - return strings.Replace(result, `/`, `\/`, -1) -} diff --git a/cucumber-expressions/go/cucumber_expression_regexp_test.go b/cucumber-expressions/go/cucumber_expression_regexp_test.go index 48ba3efd71..1202a6410d 100644 --- a/cucumber-expressions/go/cucumber_expression_regexp_test.go +++ b/cucumber-expressions/go/cucumber_expression_regexp_test.go @@ -1,8 +1,8 @@ package cucumberexpressions import ( - "testing" "github.com/stretchr/testify/require" + "testing" ) func TestCucumberExpressionRegExpTranslation(t *testing.T) { @@ -61,7 +61,6 @@ func TestCucumberExpressionRegExpTranslation(t *testing.T) { ) }) - } func assertRegexp(t *testing.T, expression string, expectedRegexp string) { diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go new file mode 100644 index 0000000000..b67cffe2f3 --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -0,0 +1,85 @@ +package cucumberexpressions + +import ( + "regexp" + "strings" +) + +type tokenizer func(expression string, current int) (int, *token) + +var tokenizers = []tokenizer{ + tokenizePattern(whiteSpaceEscaped, regexp.MustCompile(`\\\s`)), + tokenizePattern(whiteSpace, regexp.MustCompile(`\s+`)), + + tokenizeString(beginOptionalEscaped, "\\("), + tokenizeString(beginOptional, "("), + + tokenizeString(endOptionalEscaped, "\\)"), + tokenizeString(endOptional, ")"), + + tokenizeString(beginParameterEscaped, "\\{"), + tokenizeString(beginParameter, "{"), + + tokenizeString(endParameterEscaped, "\\}"), + tokenizeString(endParameter, "}"), + + tokenizeString(alternationEscaped, "\\/"), + tokenizeString(alternation, "/"), + + tokenizeString(escapeEscaped, "\\\\"), + tokenizeString(escape, "\\"), + + // Should be `.` but this creates a nicer parse tree. + tokenizePattern(text, regexp.MustCompile(`[^(){}\\/\s]+`)), +} + +/* + * token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | + * '\{' | '{' | '\}' | '}' | '\/' | '/' | '\\' | '\' | . + */ +func tokenize(expression string) ([]token, error) { + length := len(expression) + tokens := make([]token, 0) + + current := 0 + for current < length { + tokenized := false + for _, tokenizer := range tokenizers { + consumed, token := tokenizer(expression, current) + if consumed != 0 { + tokens = append(tokens, *token) + current += consumed + tokenized = true + break + } + } + if !tokenized { + // Can't happen if configured properly + // Leave in to avoid looping if not configured properly + return tokens, NewCucumberExpressionError("Could not tokenize" + expression) + } + } + + return tokens, nil +} + +func tokenizeString(tokenType tokenType, pattern string) tokenizer { + return func(expression string, current int) (int, *token) { + if !strings.HasPrefix(expression[current:], pattern) { + return 0, nil + } + return len(pattern), &token{pattern, tokenType} + } +} + +func tokenizePattern(tokenType tokenType, regexp *regexp.Regexp) tokenizer { + return func(expression string, current int) (int, *token) { + tail := expression[current:] + loc := regexp.FindStringIndex(tail) + if loc == nil || loc[0] != 0 { + return 0, nil + } + match := tail[0:loc[1]] + return loc[1], &token{match, tokenType} + } +} diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go new file mode 100644 index 0000000000..c68a25ebfc --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -0,0 +1,117 @@ +package cucumberexpressions + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestCucumberExpressionTokenizer(t *testing.T) { + t.Run("empty string", func(t *testing.T) { + assertContains(t, "", []token{}) + }) + t.Run("phrase", func(t *testing.T) { + assertContains(t, "three blind mice", []token{ + {"three", text}, + {" ", whiteSpace}, + {"blind", text}, + {" ", whiteSpace}, + {"mice", text}, + }) + }) + + t.Run("optional", func(t *testing.T) { + assertContains(t, "(blind)", []token{ + {"(", beginOptional}, + {"blind", text}, + {")", endOptional}, + }) + }) + + t.Run("escaped optional", func(t *testing.T) { + assertContains(t, "\\(blind\\)", []token{ + {"\\(", beginOptionalEscaped}, + {"blind", text}, + {"\\)", endOptionalEscaped}, + }) + }) + + t.Run("optional phrase", func(t *testing.T) { + assertContains(t, "three (blind) mice", []token{ + {"three", text}, + {" ", whiteSpace}, + {"(", beginOptional}, + {"blind", text}, + {")", endOptional}, + {" ", whiteSpace}, + {"mice", text}, + }) + }) + + t.Run("parameter", func(t *testing.T) { + assertContains(t, "{string}", []token{ + {"{", beginParameter}, + {"string", text}, + {"}", endParameter}, + }) + }) + + t.Run("escaped parameter", func(t *testing.T) { + assertContains(t, "\\{string\\}", []token{ + {"\\{", beginParameterEscaped}, + {"string", text}, + {"\\}", endParameterEscaped}, + }) + }) + + t.Run("parameter phrase", func(t *testing.T) { + assertContains(t, "three {string} mice", []token{ + {"three", text}, + {" ", whiteSpace}, + {"{", beginParameter}, + {"string", text}, + {"}", endParameter}, + {" ", whiteSpace}, + {"mice", text}, + }) + }) + t.Run("alternation", func(t *testing.T) { + assertContains(t, "blind/cripple", []token{ + {"blind", text}, + {"/", alternation}, + {"cripple", text}, + }) + }) + + t.Run("escaped alternation", func(t *testing.T) { + assertContains(t, "blind\\ and\\ famished\\/cripple mice", []token{ + {"blind", text}, + {"\\ ", whiteSpaceEscaped}, + {"and", text}, + {"\\ ", whiteSpaceEscaped}, + {"famished", text}, + {"\\/", alternationEscaped}, + {"cripple", text}, + {" ", whiteSpace}, + {"mice", text}, + }) + }) + + t.Run("alternation phrase", func(t *testing.T) { + assertContains(t, "three blind/cripple mice", []token{ + {"three", text}, + {" ", whiteSpace}, + {"blind", text}, + {"/", alternation}, + {"cripple", text}, + {" ", whiteSpace}, + {"mice", text}, + }) + }) + +} + +func assertContains(t *testing.T, expression string, expected []token) { + tokens, err := tokenize(expression) + require.NoError(t, err) + require.Equal(t, expected, tokens) +} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 014cb23b43..2514a909cb 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -26,11 +26,11 @@ final class CucumberExpressionTokenizer { - private interface Tokenize { + private interface Tokenizer { int tokenize(List tokens, String expression, int current); } - private static final List tokenizers = Arrays.asList( + private static final List tokenizers = Arrays.asList( tokenizePattern(WHITE_SPACE_ESCAPED, Pattern.compile("\\\\\\s")), tokenizePattern(WHITE_SPACE, Pattern.compile("\\s+")), @@ -66,10 +66,10 @@ List tokenize(String expression) { int current = 0; while (current < length) { boolean tokenized = false; - for (Tokenize tokenizer : tokenizers) { - int consumedChars = tokenizer.tokenize(tokens, expression, current); - if (consumedChars != 0) { - current += consumedChars; + for (Tokenizer tokenizer : tokenizers) { + int consumed = tokenizer.tokenize(tokens, expression, current); + if (consumed != 0) { + current += consumed; tokenized = true; break; } @@ -83,22 +83,19 @@ List tokenize(String expression) { return tokens; } - private static Tokenize tokenizeCharacter(Token.Type type, char character) { + private static Tokenizer tokenizeCharacter(Token.Type type, char character) { return (tokens, expression, current) -> { - char c = expression.charAt(current); - if (character != c) { + if (character != expression.charAt(current)) { return 0; } - Token e = new Token("" + character, type); - tokens.add(e); + tokens.add(new Token("" + character, type)); return 1; }; } - private static Tokenize tokenizeString(Token.Type type, String string) { + private static Tokenizer tokenizeString(Token.Type type, String string) { return (tokens, expression, current) -> { - boolean c = expression.regionMatches(current, string, 0, string.length()); - if (!c) { + if (!expression.regionMatches(current, string, 0, string.length())) { return 0; } tokens.add(new Token(string, type)); @@ -106,7 +103,7 @@ private static Tokenize tokenizeString(Token.Type type, String string) { }; } - private static Tokenize tokenizePattern(Token.Type type, Pattern pattern) { + private static Tokenizer tokenizePattern(Token.Type type, Pattern pattern) { return (tokens, expression, current) -> { String tail = expression.substring(current); Matcher matcher = pattern.matcher(tail); diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index a08f6d73e9..ee1dcc7dda 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -107,23 +107,25 @@ void parameterPhrase() { @Test void alternation() { - assertThat(tokenizer.tokenize("(blind)"), contains( - new Token("(", BEGIN_OPTIONAL), + assertThat(tokenizer.tokenize("blind/cripple"), contains( new Token("blind", TEXT), - new Token(")", END_OPTIONAL) + new Token("/", ALTERNATION), + new Token("cripple", TEXT) )); } @Test void escapedAlternation() { - assertThat(tokenizer.tokenize("blind\\ and\\ famished\\/cripple"), contains( + assertThat(tokenizer.tokenize("blind\\ and\\ famished\\/cripple mice"), contains( new Token("blind", TEXT), new Token("\\ ", WHITE_SPACE_ESCAPED), new Token("and", TEXT), new Token("\\ ", WHITE_SPACE_ESCAPED), new Token("famished", TEXT), new Token("\\/", ALTERNATION_ESCAPED), - new Token("cripple", TEXT) + new Token("cripple", TEXT), + new Token(" ", WHITE_SPACE), + new Token("mice", TEXT) )); } From a1484ef88b5de9ef121b78c0bc359396b8e3bf7e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 7 Nov 2019 23:58:49 +0100 Subject: [PATCH 050/183] parse optional and parameters --- cucumber-expressions/go/ast.go | 26 ++- .../go/cucumber_expression_parser.go | 143 ++++++++++++++++ .../go/cucumber_expression_parser_test.go | 153 ++++++++++++++++++ .../go/cucumber_expression_tokenizer.go | 16 +- .../go/cucumber_expression_tokenizer_test.go | 12 +- .../CucumberExpressionParser.java | 56 +++---- .../CucumberExpressionParserTest.java | 3 +- 7 files changed, 364 insertions(+), 45 deletions(-) create mode 100644 cucumber-expressions/go/cucumber_expression_parser.go create mode 100644 cucumber-expressions/go/cucumber_expression_parser_test.go diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index e056bae974..ae3803c839 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -1,10 +1,27 @@ package cucumberexpressions +type nodeType int + +const ( + textNode nodeType = iota + optionalNode + alternationNode + alternativeNode + parameterNode + expressionNode +) + +type astNode struct { + nodeType nodeType + nodes []astNode + token token +} + type tokenType int const ( startOfLine tokenType = iota - EndOfLine + endOfLine // In order of precedence whiteSpaceEscaped whiteSpace @@ -27,3 +44,10 @@ type token struct { text string tokenType tokenType } + +var beginOptionalToken = token{"(", beginOptional} +var endOptionalToken = token{")", endOptional} +var beginParameterToken = token{"{", beginParameter} +var endParameterToken = token{"}", endParameter} +var alternationToken = token{"/", alternation} +var escapeToken = token{"\\", escape} diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go new file mode 100644 index 0000000000..ecb1f61e80 --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -0,0 +1,143 @@ +package cucumberexpressions + +var errorNode = astNode{textNode, []astNode{}, token{}} + +type parser func(expression []token, current int) (int, astNode) + +var parameterParser = parseBetween( + optionalNode, + beginOptional, + endOptional, + textParser, +) + +var optionalParser = parseBetween( + parameterNode, + beginParameter, + endParameter, + parameterParser, + textParser, +) + +var cucumberExpressionParsers = []parser{ + optionalParser, + parameterParser, + textParser, +} + +var textParser = func(expression []token, current int) (int, astNode) { + currentToken := expression[current] + return 1, astNode{textNode, []astNode{}, unEscape(currentToken)} +} + +func unEscape(t token) token { + switch t.tokenType { + case whiteSpaceEscaped: + return token{t.text, whiteSpace} + case beginOptionalEscaped: + return beginOptionalToken + case endOptionalEscaped: + return endOptionalToken + case beginParameterEscaped: + return beginParameterToken + case endParameterEscaped: + return endParameterToken + case alternationEscaped: + return alternationToken + case escapeEscaped: + return escapeToken + default: + return t + } +} + +func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, parsers ...parser) parser { + return func(expression []token, current int) (int, astNode) { + if !lookingAt(expression, current, beginToken) { + return 0, errorNode + } + + subCurrent := current + 1 + consumed, subAst := parseTokensUntil(parsers, expression, subCurrent, endToken) + subCurrent += consumed + + // endToken not found + if lookingAt(expression, subCurrent, endOfLine) { + return 0, errorNode + } + if !lookingAt(expression, subCurrent, endToken) { + return 0, errorNode + } + // consumes endToken + return subCurrent + 1 - current, astNode{nodeType, subAst, token{}} + } +} + +/* + * cucumber-expression := ( alternation | optional | parameter | text )* + */ +func parse(expression string) (astNode, error) { + tokens, err := tokenize(expression) + if err != nil { + return errorNode, err + } + consumed, ast := parseTokensUntil(cucumberExpressionParsers, tokens, 0, endOfLine) + if consumed != len(tokens) { + // Can't happen if configured properly + return errorNode, NewCucumberExpressionError("Could not parse" + expression) + } + + return astNode{expressionNode, ast, token{}}, nil +} + +func parseTokensUntil(parsers []parser, expresion []token, startAt int, endTokens ...tokenType) (int, []astNode) { + ast := make([]astNode, 0) + current := startAt + size := len(expresion) + for current < size { + if lookingAtAny(expresion, current, endTokens) { + break + } + consumed, node := parseToken(parsers, expresion, current) + if consumed == 0 { + break + } + current += consumed + ast = append(ast, node) + } + + return current - startAt, ast +} + +func parseToken(parsers []parser, expresion []token, startAt int) (int, astNode) { + for _, parser := range parsers { + consumed, node := parser(expresion, startAt) + if consumed != 0 { + return consumed, node + } + } + return 0, errorNode +} + +func lookingAtAny(expression []token, at int, tokens []tokenType) bool { + for _, token := range tokens { + if lookingAt(expression, at, token) { + return true + } + } + return false +} + +func lookingAt(expression []token, at int, token tokenType) bool { + size := len(expression) + if at < 0 || at >= size { + if token == startOfLine { + return at < 0 + } + if token == endOfLine { + return at >= size + } + return false + } + return expression[at].tokenType == token +} diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go new file mode 100644 index 0000000000..f14c3ec414 --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -0,0 +1,153 @@ +package cucumberexpressions + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestCucumberExpressionParser(t *testing.T) { + var assertAst = func(t *testing.T, expression string, expected astNode) { + ast, err := parse(expression) + require.NoError(t, err) + require.Equal(t, expected, ast) + } + + t.Run("empty string", func(t *testing.T) { + assertAst(t, "", astNode{ + expressionNode, + []astNode{}, + token{}, + }) + }) + + t.Run("phrase", func(t *testing.T) { + assertAst(t, "three blind mice", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"blind", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + }, + token{}, + }) + }) + + t.Run("optional", func(t *testing.T) { + assertAst(t, "(blind)", astNode{ + expressionNode, + []astNode{ + {optionalNode, + []astNode{ + {textNode, []astNode{}, token{"blind", text}}, + }, + token{}, + }, + }, + token{}, + }) + }) + + t.Run("parameter", func(t *testing.T) { + assertAst(t, "{string}", astNode{ + expressionNode, + []astNode{ + {parameterNode, + []astNode{ + {textNode, []astNode{}, token{"string", text}}, + }, + token{}, + }, + }, + token{}, + }) + }) + + t.Run("anonymous parameter", func(t *testing.T) { + assertAst(t, "{}", astNode{ + expressionNode, + []astNode{ + {parameterNode, + []astNode{}, + token{}, + }, + }, + token{}, + }) + }) + + t.Run("optional phrase", func(t *testing.T) { + assertAst(t, "three (blind) mice", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {optionalNode, []astNode{ + {textNode, []astNode{}, token{"blind", text}}, + }, token{}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + }, + token{}, + }) + }) + + t.Run("slash", func(t *testing.T) { + assertAst(t, "\\", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"\\", escape}}, + }, + token{}, + }) + }) + + t.Run("opening brace", func(t *testing.T) { + assertAst(t, "{", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"{", beginParameter}}, + }, + token{}, + }) + }) + + t.Run("opening parenthesis", func(t *testing.T) { + assertAst(t, "(", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"(", beginOptional}}, + }, + token{}, + }) + }) + + t.Run("escaped opening parenthesis", func(t *testing.T) { + assertAst(t, "\\(", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"(", beginOptional}}, + }, + token{}, + }) + }) + + t.Run("escaped optional phrase", func(t *testing.T) { + assertAst(t, "three \\(blind) mice", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"(", beginOptional}}, + {textNode, []astNode{}, token{"blind", text}}, + {textNode, []astNode{}, token{")", endOptional}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + }, + token{}, + }) + }) + + //TODO: escapedOptionalFollowedByOptional +} diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index b67cffe2f3..72d79b5f65 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -5,7 +5,7 @@ import ( "strings" ) -type tokenizer func(expression string, current int) (int, *token) +type tokenizer func(expression string, current int) (int, token) var tokenizers = []tokenizer{ tokenizePattern(whiteSpaceEscaped, regexp.MustCompile(`\\\s`)), @@ -47,7 +47,7 @@ func tokenize(expression string) ([]token, error) { for _, tokenizer := range tokenizers { consumed, token := tokenizer(expression, current) if consumed != 0 { - tokens = append(tokens, *token) + tokens = append(tokens, token) current += consumed tokenized = true break @@ -64,22 +64,22 @@ func tokenize(expression string) ([]token, error) { } func tokenizeString(tokenType tokenType, pattern string) tokenizer { - return func(expression string, current int) (int, *token) { + return func(expression string, current int) (int, token) { if !strings.HasPrefix(expression[current:], pattern) { - return 0, nil + return 0, token{"", tokenType} } - return len(pattern), &token{pattern, tokenType} + return len(pattern), token{pattern, tokenType} } } func tokenizePattern(tokenType tokenType, regexp *regexp.Regexp) tokenizer { - return func(expression string, current int) (int, *token) { + return func(expression string, current int) (int, token) { tail := expression[current:] loc := regexp.FindStringIndex(tail) if loc == nil || loc[0] != 0 { - return 0, nil + return 0, token{"", tokenType} } match := tail[0:loc[1]] - return loc[1], &token{match, tokenType} + return loc[1], token{match, tokenType} } } diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index c68a25ebfc..ba1995d90a 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -6,6 +6,12 @@ import ( ) func TestCucumberExpressionTokenizer(t *testing.T) { + var assertContains = func(t *testing.T, expression string, expected []token) { + tokens, err := tokenize(expression) + require.NoError(t, err) + require.Equal(t, expected, tokens) + } + t.Run("empty string", func(t *testing.T) { assertContains(t, "", []token{}) }) @@ -109,9 +115,3 @@ func TestCucumberExpressionTokenizer(t *testing.T) { }) } - -func assertContains(t *testing.T, expression string, expected []token) { - tokens, err := tokenize(expression) - require.NoError(t, err) - require.Equal(t, expected, tokens) -} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 633d183ae5..64d2d61c00 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -25,14 +25,14 @@ final class CucumberExpressionParser { - private interface Parse { + private interface Parser { int parse(List ast, List expression, int current); } /* * text := token */ - private static final Parse textParser = (ast, expression, current) -> { + private static final Parser textParser = (ast, expression, current) -> { Token currentToken = expression.get(current); Token unescaped = unEscape(currentToken); ast.add(new Text(unescaped)); @@ -63,7 +63,7 @@ private static Token unEscape(Token currentToken) { /* * parameter := '{' + text* + '}' */ - private static final Parse parameterParser = parseBetween( + private static final Parser parameterParser = parseBetween( BEGIN_PARAMETER, END_PARAMETER, singletonList(textParser), @@ -74,17 +74,17 @@ private static Token unEscape(Token currentToken) { * optional := '(' + option* + ')' * option := parameter | text */ - private static final Parse optionalParser = parseBetween( + private static final Parser optionalParser = parseBetween( BEGIN_OPTIONAL, END_OPTIONAL, asList(parameterParser, textParser), Optional::new ); - private static Parse parseBetween( + private static Parser parseBetween( Type beginToken, Type endToken, - List parsers, + List parsers, Function, AstNode> create) { return (ast, expression, current) -> { if (!lookingAt(expression, current, beginToken)) { @@ -115,7 +115,7 @@ private static Parse parseBetween( // alternation := alternative* + ( '/' + alternative* )+ }; - private static Parse alternativeSeparator = (ast, expression, current) -> { + private static Parser alternativeSeparator = (ast, expression, current) -> { if (!lookingAt(expression, current, ALTERNATION)) { return 0; } @@ -123,7 +123,7 @@ private static Parse parseBetween( return 1; }; - private static final List alternativeParsers = asList( + private static final List alternativeParsers = asList( alternativeSeparator, optionalParser, parameterParser, @@ -135,7 +135,7 @@ private static Parse parseBetween( * boundary := whitespace | ^ | $ * alternative: = optional | parameter | text */ - private static final Parse alternationParser = (ast, expression, current) -> { + private static final Parser alternationParser = (ast, expression, current) -> { int previous = current - 1; if (!lookingAt(expression, previous, START_OF_LINE) && !lookingAt(expression, previous, WHITE_SPACE)) { @@ -154,7 +154,7 @@ private static Parse parseBetween( return consumed; }; - private static final List cucumberExpressionParsers = asList( + private static final List cucumberExpressionParsers = asList( alternationParser, optionalParser, parameterParser, @@ -168,50 +168,51 @@ Expression parse(String expression) { CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); List tokens = tokenizer.tokenize(expression); List ast = new ArrayList<>(); - parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, END_OF_LINE); + int consumed = parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, END_OF_LINE); + if (consumed != tokens.size()) { + // If configured correctly this will never happen + throw new IllegalStateException("Could not parse " + expression); + } return new Expression(ast); } - private static int parseTokensUntil(List parsers, + private static int parseTokensUntil(List parsers, List ast, List expression, int startAt, Type... endTokens) { int current = startAt; - while (current < expression.size()) { + int size = expression.size(); + while (current < size) { if (lookingAt(expression, current, endTokens)) { break; } int consumed = parseToken(parsers, ast, expression, current); if (consumed == 0) { - // If configured correctly this will never happen - // Keep to avoid infinite loop just in case - throw new IllegalStateException("Could not parse " + expression); + break; } current += consumed; } return current - startAt; } - private static int parseToken(List parsers, + private static int parseToken(List parsers, List ast, List expression, int startAt) { - int current = startAt; - for (Parse parser : parsers) { - int consumed = parser.parse(ast, expression, current); + for (Parser parser : parsers) { + int consumed = parser.parse(ast, expression, startAt); if (consumed != 0) { - current += consumed; - break; + return consumed; } } - return current - startAt; + return 0; } - private static boolean lookingAt(List expression, int current, Type... endTokens) { - for (Type endToken : endTokens) { - if (lookingAt(expression, current, endToken)) { + private static boolean lookingAt(List expression, int at, Type... tokens) { + for (Type token : tokens) { + if (lookingAt(expression, at, token)) { return true; } } @@ -228,8 +229,7 @@ private static boolean lookingAt(List expression, int at, Type token) { } return false; } - Token currentToken = expression.get(at); - return currentToken.type == token; + return expression.get(at).type == token; } private static List> splitOnAlternation(List tokens) { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 2156d0a42c..adf5c9a9fe 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -4,7 +4,6 @@ import io.cucumber.cucumberexpressions.AstNode.Optional; import io.cucumber.cucumberexpressions.AstNode.Parameter; import io.cucumber.cucumberexpressions.AstNode.Text; -import io.cucumber.cucumberexpressions.AstNode.Token; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; @@ -76,7 +75,7 @@ void slash() { } @Test - void brace() { + void openingBrace() { assertThat(astOf("{"), contains( node("{", Text.class) )); From bebe51aedb6522b00245c6c11baf4e3930d80a86 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 14:26:36 +0100 Subject: [PATCH 051/183] parse alternation --- cucumber-expressions/go/ast.go | 6 + .../go/cucumber_expression_parser.go | 153 +++++++++++---- .../go/cucumber_expression_parser_test.go | 182 +++++++++++++++++- .../cucumber/cucumberexpressions/AstNode.java | 7 + .../CucumberExpressionParser.java | 19 +- 5 files changed, 312 insertions(+), 55 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index ae3803c839..8cc4257516 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -17,6 +17,12 @@ type astNode struct { token token } +// Marker. This way we don't need to model the +// the tail end of alternation in the AST: +// +// alternation := alternative* + ( '/' + alternative* )+ +var alternativeSeparator = astNode{alternativeNode, []astNode{}, token{"/", alternation}} + type tokenType int const ( diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index ecb1f61e80..75d5d8504c 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -1,9 +1,40 @@ package cucumberexpressions -var errorNode = astNode{textNode, []astNode{}, token{}} +var nullNode = astNode{textNode, []astNode{}, token{}} type parser func(expression []token, current int) (int, astNode) +/* + * parameter := '{' + text* + '}' + */ +var textParser = func(expression []token, current int) (int, astNode) { + unEscape := func(t token) token { + switch t.tokenType { + case whiteSpaceEscaped: + return token{t.text[1:], whiteSpace} + case beginOptionalEscaped: + return beginOptionalToken + case endOptionalEscaped: + return endOptionalToken + case beginParameterEscaped: + return beginParameterToken + case endParameterEscaped: + return endParameterToken + case alternationEscaped: + return alternationToken + case escapeEscaped: + return escapeToken + default: + return t + } + } + currentToken := expression[current] + return 1, astNode{textNode, []astNode{}, unEscape(currentToken)} +} + +/* + * parameter := '{' + text* + '}' + */ var parameterParser = parseBetween( optionalNode, beginOptional, @@ -11,6 +42,10 @@ var parameterParser = parseBetween( textParser, ) +/* + * optional := '(' + option* + ')' + * option := parameter | text + */ var optionalParser = parseBetween( parameterNode, beginParameter, @@ -19,42 +54,10 @@ var optionalParser = parseBetween( textParser, ) -var cucumberExpressionParsers = []parser{ - optionalParser, - parameterParser, - textParser, -} - -var textParser = func(expression []token, current int) (int, astNode) { - currentToken := expression[current] - return 1, astNode{textNode, []astNode{}, unEscape(currentToken)} -} - -func unEscape(t token) token { - switch t.tokenType { - case whiteSpaceEscaped: - return token{t.text, whiteSpace} - case beginOptionalEscaped: - return beginOptionalToken - case endOptionalEscaped: - return endOptionalToken - case beginParameterEscaped: - return beginParameterToken - case endParameterEscaped: - return endParameterToken - case alternationEscaped: - return alternationToken - case escapeEscaped: - return escapeToken - default: - return t - } -} - func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, parsers ...parser) parser { return func(expression []token, current int) (int, astNode) { if !lookingAt(expression, current, beginToken) { - return 0, errorNode + return 0, nullNode } subCurrent := current + 1 @@ -63,28 +66,96 @@ func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, p // endToken not found if lookingAt(expression, subCurrent, endOfLine) { - return 0, errorNode + return 0, nullNode } if !lookingAt(expression, subCurrent, endToken) { - return 0, errorNode + return 0, nullNode } // consumes endToken return subCurrent + 1 - current, astNode{nodeType, subAst, token{}} } } +var alternativeSeparatorParser = func(expression []token, current int) (int, astNode) { + if !lookingAt(expression, current, alternation) { + return 0, nullNode + } + return 1, alternativeSeparator +} + +var alternativeParsers = []parser{ + alternativeSeparatorParser, + optionalParser, + parameterParser, + textParser, +} + +/* + * alternation := (?<=boundary) + alternative* + ( '/' + alternative* )+ + (?=boundary) + * boundary := whitespace | ^ | $ + * alternative: = optional | parameter | text + */ +var alternationParser = func(expression []token, current int) (int, astNode) { + previous := current - 1 + if !lookingAtAny(expression, previous, startOfLine, whiteSpace) { + return 0, nullNode + } + consumed, subAst := parseTokensUntil(alternativeParsers, expression, current, whiteSpace, endOfLine) + + var contains = func(s []astNode, node astNode) bool { + for _, a := range s { + if a.nodeType == node.nodeType { + return true + } + } + return false + } + + if !contains(subAst, alternativeSeparator) { + return 0, nullNode + } + + splitOn := func(subAst []astNode, separator astNode) []astNode { + alternatives := make([]astNode, 0) + alternative := make([]astNode, 0) + for _, node := range subAst { + if node.nodeType == separator.nodeType { + alternatives = append(alternatives, astNode{alternativeNode, alternative, token{}}) + alternative = make([]astNode, 0) + } else { + alternative = append(alternative, node) + } + } + alternatives = append(alternatives, astNode{alternativeNode, alternative, token{}}) + return alternatives + } + + alternatives := splitOn(subAst, alternativeSeparator) + // Does not consume right hand boundary token + return consumed, astNode{alternationNode, alternatives, token{}} + +} + +var cucumberExpressionParsers = []parser{ + alternationParser, + optionalParser, + parameterParser, + textParser, +} + + /* * cucumber-expression := ( alternation | optional | parameter | text )* */ func parse(expression string) (astNode, error) { tokens, err := tokenize(expression) if err != nil { - return errorNode, err + return nullNode, err } consumed, ast := parseTokensUntil(cucumberExpressionParsers, tokens, 0, endOfLine) if consumed != len(tokens) { // Can't happen if configured properly - return errorNode, NewCucumberExpressionError("Could not parse" + expression) + return nullNode, NewCucumberExpressionError("Could not parse" + expression) } return astNode{expressionNode, ast, token{}}, nil @@ -95,7 +166,7 @@ func parseTokensUntil(parsers []parser, expresion []token, startAt int, endToken current := startAt size := len(expresion) for current < size { - if lookingAtAny(expresion, current, endTokens) { + if lookingAtAny(expresion, current, endTokens...) { break } consumed, node := parseToken(parsers, expresion, current) @@ -116,10 +187,10 @@ func parseToken(parsers []parser, expresion []token, startAt int) (int, astNode) return consumed, node } } - return 0, errorNode + return 0, nullNode } -func lookingAtAny(expression []token, at int, tokens []tokenType) bool { +func lookingAtAny(expression []token, at int, tokens ...tokenType) bool { for _, token := range tokens { if lookingAt(expression, at, token) { return true diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index f14c3ec414..71ca6ec7da 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -149,5 +149,185 @@ func TestCucumberExpressionParser(t *testing.T) { }) }) - //TODO: escapedOptionalFollowedByOptional + t.Run("escaped optional followed by optional", func(t *testing.T) { + assertAst(t, "three \\((very) blind) mice", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"(", beginOptional}}, + {optionalNode, []astNode{ + {textNode, []astNode{}, token{"very", text}}, + }, token{}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"blind", text}}, + {textNode, []astNode{}, token{")", endOptional}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + }, + token{}, + }) + }) + + t.Run("optional containing escaped optional", func(t *testing.T) { + assertAst(t, "three ((very\\) blind) mice", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {optionalNode, []astNode{ + {textNode, []astNode{}, token{"(", beginOptional}}, + {textNode, []astNode{}, token{"very", text}}, + {textNode, []astNode{}, token{")", endOptional}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"blind", text}}, + }, token{}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + }, + token{}, + }) + }) + + t.Run("alternation", func(t *testing.T) { + assertAst(t, "mice/rats", astNode{ + expressionNode, + []astNode{ + {alternationNode, []astNode{ + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"mice", text}}, + }, token{}}, + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"rats", text}}, + }, token{}}, + }, token{}}, + }, + token{}, + }) + }) + + t.Run("escaped alternation", func(t *testing.T) { + assertAst(t, "mice\\/rats", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"mice", text}}, + {textNode, []astNode{}, token{"/", alternation}}, + {textNode, []astNode{}, token{"rats", text}}, + }, + token{}, + }) + }) + + t.Run("alternation phrase", func(t *testing.T) { + assertAst(t, "three hungry/blind mice", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {alternationNode, []astNode{ + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"hungry", text}}, + }, token{}}, + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"blind", text}}, + }, token{}}, + }, token{}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + }, + token{}, + }) + }) + + t.Run("alternation with whitespace", func(t *testing.T) { + assertAst(t, "\\ three\\ hungry/blind\\ mice\\ ", astNode{ + expressionNode, + []astNode{ + {alternationNode, []astNode{ + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"hungry", text}}, + }, token{}}, + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"blind", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + }, token{}}, + }, token{}}, + }, + token{}, + }) + }) + + t.Run("alternation with unused end optional", func(t *testing.T) { + assertAst(t, "three )blind\\ mice/rats", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {alternationNode, []astNode{ + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{")", endOptional}}, + {textNode, []astNode{}, token{"blind", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + }, token{}}, + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"rats", text}}, + }, token{}}, + }, token{}}, + }, + token{}, + }) + }) + + t.Run("alternation with unused start optional", func(t *testing.T) { + assertAst(t, "three blind\\ mice/rats(", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {alternationNode, []astNode{ + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"blind", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"mice", text}}, + }, token{}}, + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"rats", text}}, + {textNode, []astNode{}, token{"(", beginOptional}}, + }, token{}}, + }, token{}}, + }, + token{}, + }) + }) + + t.Run("alternation with unused start optional", func(t *testing.T) { + assertAst(t, "three blind\\ rat/cat(s)", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"three", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {alternationNode, []astNode{ + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"blind", text}}, + {textNode, []astNode{}, token{" ", whiteSpace}}, + {textNode, []astNode{}, token{"rat", text}}, + }, token{}}, + {alternativeNode, []astNode{ + {textNode, []astNode{}, token{"cat", text}}, + {optionalNode, []astNode{ + {textNode, []astNode{}, token{"s", text}}, + }, token{}}, + }, token{}}, + }, token{}}, + }, + token{}, + }) + }) + } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java index af2b517fa1..7e45275ea8 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java @@ -7,6 +7,13 @@ abstract class AstNode { + static final AstNode ALTERNATIVE_SEPARATOR = new AstNode() { + // Marker. This way we don't need to model the + // the tail end of alternation in the AST: + // + // alternation := alternative* + ( '/' + alternative* )+ + }; + static final class Expression extends AstNode { private final List nodes; diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 64d2d61c00..7eca1f13c5 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.function.Function; +import static io.cucumber.cucumberexpressions.AstNode.ALTERNATIVE_SEPARATOR; import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION; import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL; import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER; @@ -108,13 +109,6 @@ private static Parser parseBetween( }; } - private static final AstNode ALTERNATIVE_SEPARATOR = new AstNode() { - // Marker. This way we don't need to model the - // the tail end of alternation in the AST: - // - // alternation := alternative* + ( '/' + alternative* )+ - }; - private static Parser alternativeSeparator = (ast, expression, current) -> { if (!lookingAt(expression, current, ALTERNATION)) { return 0; @@ -137,8 +131,7 @@ private static Parser parseBetween( */ private static final Parser alternationParser = (ast, expression, current) -> { int previous = current - 1; - if (!lookingAt(expression, previous, START_OF_LINE) - && !lookingAt(expression, previous, WHITE_SPACE)) { + if (!lookingAt(expression, previous, START_OF_LINE, WHITE_SPACE)) { return 0; } @@ -148,7 +141,7 @@ private static Parser parseBetween( return 0; } - List> alternatives = splitOnAlternation(subAst); + List> alternatives = splitOn(subAst, ALTERNATIVE_SEPARATOR); ast.add(new Alternation(alternatives)); // Does not consume right hand boundary token return consumed; @@ -232,12 +225,12 @@ private static boolean lookingAt(List expression, int at, Type token) { return expression.get(at).type == token; } - private static List> splitOnAlternation(List tokens) { + private static List> splitOn(List astNode, T separator) { List> alternatives = new ArrayList<>(); List alternative = new ArrayList<>(); alternatives.add(alternative); - for (T token : tokens) { - if (ALTERNATIVE_SEPARATOR.equals(token)) { + for (T token : astNode) { + if (separator.equals(token)) { alternative = new ArrayList<>(); alternatives.add(alternative); } else { From 17e9afae7760803ad398c57521ce4d0fe9be1c83 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 15:44:27 +0100 Subject: [PATCH 052/183] check entire ast in java --- .../go/cucumber_expression_parser.go | 10 +- .../io/cucumber/cucumberexpressions/Ast.java | 149 ++++++++ .../cucumber/cucumberexpressions/AstNode.java | 171 --------- .../CucumberExpression.java | 119 +++--- .../CucumberExpressionParser.java | 74 ++-- .../CucumberExpressionTokenizer.java | 32 +- .../CucumberExpressionParserTest.java | 338 +++++++++++------- .../CucumberExpressionTokenizerTest.java | 28 +- 8 files changed, 493 insertions(+), 428 deletions(-) create mode 100644 cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java delete mode 100644 cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 75d5d8504c..703beeeea2 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -115,24 +115,22 @@ var alternationParser = func(expression []token, current int) (int, astNode) { return 0, nullNode } - splitOn := func(subAst []astNode, separator astNode) []astNode { + splitAlternatives := func(subAst []astNode) []astNode { alternatives := make([]astNode, 0) alternative := make([]astNode, 0) for _, node := range subAst { - if node.nodeType == separator.nodeType { + if node.nodeType == alternativeSeparator.nodeType { alternatives = append(alternatives, astNode{alternativeNode, alternative, token{}}) alternative = make([]astNode, 0) } else { alternative = append(alternative, node) } } - alternatives = append(alternatives, astNode{alternativeNode, alternative, token{}}) - return alternatives + return append(alternatives, astNode{alternativeNode, alternative, token{}}) } - alternatives := splitOn(subAst, alternativeSeparator) // Does not consume right hand boundary token - return consumed, astNode{alternationNode, alternatives, token{}} + return consumed, astNode{alternationNode, splitAlternatives(subAst), token{}} } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java new file mode 100644 index 0000000000..49b91c3f6c --- /dev/null +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -0,0 +1,149 @@ +package io.cucumber.cucumberexpressions; + +import java.util.List; +import java.util.Objects; + +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATIVE_NODE; +import static io.cucumber.cucumberexpressions.Ast.Token.ALTERNATION; +import static java.util.Arrays.asList; + +final class Ast { + // Marker. This way we don't need to model the + // the tail end of alternation in the AST: + // + // alternation := alternative* + ( '/' + alternative* )+ + static final AstNode ALTERNATIVE_SEPARATOR = new AstNode(ALTERNATIVE_NODE, ALTERNATION); + + static final class AstNode { + + private final Type type; + private final List nodes; + private final Token token; + + AstNode(Type type, Token token) { + this(type, null, token); + } + + AstNode(Type type, AstNode... nodes) { + this(type, asList(nodes)); + } + + AstNode(Type type, List nodes) { + this(type, nodes, null); + } + + private AstNode(Type type, List nodes, Token token) { + this.type = type; + this.nodes = nodes; + this.token = token; + } + + + enum Type { + TEXT_NODE, + OPTIONAL_NODE, + ALTERNATION_NODE, + ALTERNATIVE_NODE, + PARAMETER_NODE, + EXPRESSION_NODE + } + + List getNodes() { + return nodes; + } + + Type getType() { + return type; + } + + String getText() { + return token.text; + } + + @Override + public String toString() { + return "AstNode{" + + "type=" + type + + ", nodes=" + nodes + + ", token=" + token + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AstNode astNode = (AstNode) o; + return type == astNode.type && + Objects.equals(nodes, astNode.nodes) && + Objects.equals(token, astNode.token); + } + + @Override + public int hashCode() { + return Objects.hash(type, nodes, token); + } + } + + + static final class Token { + + static final Token BEGIN_PARAMETER = new Token("{", Token.Type.BEGIN_PARAMETER); + static final Token END_PARAMETER = new Token("}", Token.Type.END_PARAMETER); + static final Token BEGIN_OPTIONAL = new Token("(", Token.Type.BEGIN_OPTIONAL); + static final Token END_OPTIONAL = new Token(")", Token.Type.END_OPTIONAL); + static final Token ESCAPE = new Token("\\", Token.Type.ESCAPE); + static final Token ALTERNATION = new Token("/", Token.Type.ALTERNATION); + + final String text; + final Token.Type type; + + Token(String text, Token.Type type) { + this.text = text; + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Token token = (Token) o; + return text.equals(token.text) && + type == token.type; + } + + @Override + public int hashCode() { + return Objects.hash(text, type); + } + + @Override + public String toString() { + return "Token{" + + "text='" + text + '\'' + + ", type=" + type + + '}'; + } + + enum Type { + START_OF_LINE, + END_OF_LINE, + // In order of precedence + WHITE_SPACE_ESCAPED, + WHITE_SPACE, + BEGIN_OPTIONAL_ESCAPED, + BEGIN_OPTIONAL, + END_OPTIONAL_ESCAPED, + END_OPTIONAL, + BEGIN_PARAMETER_ESCAPED, + BEGIN_PARAMETER, + END_PARAMETER_ESCAPED, + END_PARAMETER, + ALTERNATION_ESCAPED, + ALTERNATION, + ESCAPE_ESCAPED, + ESCAPE, + TEXT; + } + } +} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java deleted file mode 100644 index 7e45275ea8..0000000000 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/AstNode.java +++ /dev/null @@ -1,171 +0,0 @@ -package io.cucumber.cucumberexpressions; - -import java.util.List; -import java.util.Objects; - -import static java.util.stream.Collectors.joining; - -abstract class AstNode { - - static final AstNode ALTERNATIVE_SEPARATOR = new AstNode() { - // Marker. This way we don't need to model the - // the tail end of alternation in the AST: - // - // alternation := alternative* + ( '/' + alternative* )+ - }; - - static final class Expression extends AstNode { - - private final List nodes; - - Expression(List nodes) { - this.nodes = nodes; - } - - List getNodes() { - return nodes; - } - } - - static final class Text extends AstNode { - - private final Token token; - - Text(Token token) { - this.token = token; - } - - @Override - public String toString() { - return getText(); - } - - String getText() { - return token.text; - } - } - - static final class Optional extends AstNode { - - private final List optional; - - Optional(List optional) { - this.optional = optional; - } - - List getOptional() { - return optional; - } - - @Override - public String toString() { - return optional.stream() - .map(Object::toString) - .collect(joining()); - } - } - - static final class Parameter extends AstNode { - - private final List nodes; - - Parameter(List nodes) { - this.nodes = nodes; - } - - @Override - public String toString() { - return getParameterName(); - } - - String getParameterName() { - return nodes.stream() - .map(Text.class::cast) - .map(Text::getText) - .collect(joining()); - } - } - - static final class Alternation extends AstNode { - - private final List> alternatives; - - Alternation(List> alternatives) { - this.alternatives = alternatives; - } - - List> getAlternatives() { - return alternatives; - } - - @Override - public String toString() { - return getAlternatives().stream() - .map(nodes -> nodes.stream() - .map(Objects::toString) - .collect(joining())) - .collect(joining(" - ")); - } - } - - static final class Token extends AstNode { - - static final Token BEGIN_PARAMETER = new Token("{", Type.BEGIN_PARAMETER); - static final Token END_PARAMETER = new Token("}", Type.END_PARAMETER); - static final Token BEGIN_OPTIONAL = new Token("(", Type.BEGIN_OPTIONAL); - static final Token END_OPTIONAL = new Token(")", Type.END_OPTIONAL); - static final Token ESCAPE = new Token("\\", Type.ESCAPE); - static final Token ALTERNATION = new Token("/", Type.ALTERNATION); - - final String text; - final Type type; - - Token(String text, Type type) { - this.text = text; - this.type = type; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Token token = (Token) o; - return text.equals(token.text) && - type == token.type; - } - - @Override - public int hashCode() { - return Objects.hash(text, type); - } - - @Override - public String toString() { - return "Token{" + - "text='" + text + '\'' + - ", type=" + type + - '}'; - } - - enum Type { - START_OF_LINE, - END_OF_LINE, - // In order of precedence - WHITE_SPACE_ESCAPED, - WHITE_SPACE, - BEGIN_OPTIONAL_ESCAPED, - BEGIN_OPTIONAL, - END_OPTIONAL_ESCAPED, - END_OPTIONAL, - BEGIN_PARAMETER_ESCAPED, - BEGIN_PARAMETER, - END_PARAMETER_ESCAPED, - END_PARAMETER, - ALTERNATION_ESCAPED, - ALTERNATION, - ESCAPE_ESCAPED, - ESCAPE, - TEXT; - } - } -} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 153fd9cc70..83c893b69a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -1,9 +1,6 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.AstNode.Alternation; -import io.cucumber.cucumberexpressions.AstNode.Optional; -import io.cucumber.cucumberexpressions.AstNode.Parameter; -import io.cucumber.cucumberexpressions.AstNode.Text; +import io.cucumber.cucumberexpressions.Ast.AstNode; import org.apiguardian.api.API; import java.lang.reflect.Type; @@ -11,6 +8,8 @@ import java.util.List; import java.util.regex.Pattern; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; import static java.util.stream.Collectors.joining; @API(status = API.Status.STABLE) @@ -32,77 +31,70 @@ public final class CucumberExpression implements Expression { this.parameterTypeRegistry = parameterTypeRegistry; CucumberExpressionParser parser = new CucumberExpressionParser(); - AstNode.Expression ast = parser.parse(expression); + AstNode ast = parser.parse(expression); String pattern = rewriteToRegex(ast); treeRegexp = new TreeRegexp(pattern); } private String rewriteToRegex(AstNode node) { - if (node instanceof Optional) { - Optional optional = (Optional) node; - assertNoParameters(optional.getOptional(), PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - assertNotEmpty(optional.getOptional(), OPTIONAL_MAY_NOT_BE_EMPTY); - return optional.getOptional().stream() - .map(this::rewriteToRegex) - .collect(joining("", "(?:", ")?")); + switch (node.getType()) { + case OPTIONAL_NODE: + assertNoParameters(node, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); + assertNotEmpty(node, OPTIONAL_MAY_NOT_BE_EMPTY); + return node.getNodes().stream() + .map(this::rewriteToRegex) + .collect(joining("", "(?:", ")?")); + case ALTERNATION_NODE: + validateAlternation(node); + return node.getNodes() + .stream() + .map(this::rewriteToRegex) + .collect(joining("|", "(?:", ")")); + case ALTERNATIVE_NODE: + return node.getNodes().stream() + .map(this::rewriteToRegex) + .collect(joining()); + case PARAMETER_NODE: + String name = node.getNodes().stream() + .map(AstNode::getText) + .collect(joining()); + ParameterType.checkParameterTypeName(name); + ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); + if (parameterType == null) { + throw new UndefinedParameterTypeException(name); + } + parameterTypes.add(parameterType); + List regexps = parameterType.getRegexps(); + if (regexps.size() == 1) { + return "(" + regexps.get(0) + ")"; + } + return regexps.stream() + .collect(joining(")|(?:", "((?:", "))")); + case TEXT_NODE: + return escapeRegex(node.getText()); + case EXPRESSION_NODE: + return node.getNodes().stream() + .map(this::rewriteToRegex) + .collect(joining("", "^", "$")); + default: + throw new IllegalArgumentException(node.getType().name()); } - - if (node instanceof Alternation) { - Alternation alternation = (Alternation) node; - validateAlternation(alternation); - return alternation.getAlternatives() - .stream() - .map(nodes -> nodes.stream() - .map(this::rewriteToRegex) - .collect(joining())) - .collect(joining("|", "(?:", ")")); - } - - if (node instanceof Parameter) { - Parameter parameter = (Parameter) node; - String name = parameter.getParameterName(); - ParameterType.checkParameterTypeName(name); - ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); - if (parameterType == null) { - throw new UndefinedParameterTypeException(name); - } - parameterTypes.add(parameterType); - List regexps = parameterType.getRegexps(); - if (regexps.size() == 1) { - return "(" + regexps.get(0) + ")"; - } - return regexps.stream() - .collect(joining(")|(?:", "((?:", "))")); - } - - if (node instanceof Text) { - Text text = (Text) node; - return escapeRegex(text.getText()); - } - - if (node instanceof AstNode.Expression) { - AstNode.Expression xpo = (AstNode.Expression) node; - return xpo.getNodes().stream() - .map(this::rewriteToRegex) - .collect(joining("", "^", "$")); - } - - throw new IllegalArgumentException(node.getClass().getName()); } - private void assertNotEmpty(List nodes, String message) { - boolean hasTextNode = nodes + private void assertNotEmpty(AstNode node, String message) { + boolean hasTextNode = node.getNodes() .stream() - .anyMatch(Text.class::isInstance); + .map(AstNode::getType) + .anyMatch(type -> type == TEXT_NODE); if (!hasTextNode) { throw new CucumberExpressionException(message + source); } } - private void validateAlternation(Alternation alternation) { + private void validateAlternation(AstNode alternation) { // Make sure the alternative parts aren't empty and don't contain parameter types - for (List alternative : alternation.getAlternatives()) { - if (alternative.isEmpty()) { + for (AstNode alternative : alternation.getNodes()) { + if (alternative.getNodes().isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } assertNoParameters(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); @@ -110,9 +102,10 @@ private void validateAlternation(Alternation alternation) { } } - private void assertNoParameters(List alternative, String parameterTypesCannotBeAlternative) { - boolean hasParameter = alternative.stream() - .anyMatch(Parameter.class::isInstance); + private void assertNoParameters(AstNode node, String parameterTypesCannotBeAlternative) { + boolean hasParameter = node.getNodes().stream() + .map(AstNode::getType) + .anyMatch(type -> type == PARAMETER_NODE); if (hasParameter) { throw new CucumberExpressionException(parameterTypesCannotBeAlternative + source); } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 7eca1f13c5..fb7dc9e24f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -1,26 +1,27 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.AstNode.Alternation; -import io.cucumber.cucumberexpressions.AstNode.Expression; -import io.cucumber.cucumberexpressions.AstNode.Optional; -import io.cucumber.cucumberexpressions.AstNode.Parameter; -import io.cucumber.cucumberexpressions.AstNode.Text; -import io.cucumber.cucumberexpressions.AstNode.Token; -import io.cucumber.cucumberexpressions.AstNode.Token.Type; +import io.cucumber.cucumberexpressions.Ast.AstNode; +import io.cucumber.cucumberexpressions.Ast.Token; +import io.cucumber.cucumberexpressions.Ast.Token.Type; import java.util.ArrayList; import java.util.List; -import java.util.function.Function; - -import static io.cucumber.cucumberexpressions.AstNode.ALTERNATIVE_SEPARATOR; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OF_LINE; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.START_OF_LINE; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE; + +import static io.cucumber.cucumberexpressions.Ast.ALTERNATIVE_SEPARATOR; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATION_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATIVE_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.EXPRESSION_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.OPTIONAL_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OF_LINE; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.START_OF_LINE; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -36,7 +37,7 @@ private interface Parser { private static final Parser textParser = (ast, expression, current) -> { Token currentToken = expression.get(current); Token unescaped = unEscape(currentToken); - ast.add(new Text(unescaped)); + ast.add(new AstNode(TEXT_NODE, unescaped)); return 1; }; @@ -65,10 +66,10 @@ private static Token unEscape(Token currentToken) { * parameter := '{' + text* + '}' */ private static final Parser parameterParser = parseBetween( + PARAMETER_NODE, BEGIN_PARAMETER, END_PARAMETER, - singletonList(textParser), - Parameter::new + singletonList(textParser) ); /* @@ -76,17 +77,17 @@ private static Token unEscape(Token currentToken) { * option := parameter | text */ private static final Parser optionalParser = parseBetween( + OPTIONAL_NODE, BEGIN_OPTIONAL, END_OPTIONAL, - asList(parameterParser, textParser), - Optional::new + asList(parameterParser, textParser) ); private static Parser parseBetween( + AstNode.Type type, Type beginToken, Type endToken, - List parsers, - Function, AstNode> create) { + List parsers) { return (ast, expression, current) -> { if (!lookingAt(expression, current, beginToken)) { return 0; @@ -103,7 +104,7 @@ private static Parser parseBetween( if (!lookingAt(expression, subCurrent, endToken)) { return 0; } - ast.add(create.apply(subAst)); + ast.add(new AstNode(type, subAst)); // consumes endToken return subCurrent + 1 - current; }; @@ -141,8 +142,7 @@ private static Parser parseBetween( return 0; } - List> alternatives = splitOn(subAst, ALTERNATIVE_SEPARATOR); - ast.add(new Alternation(alternatives)); + ast.add(new AstNode(ALTERNATION_NODE, splitAlternatives(subAst))); // Does not consume right hand boundary token return consumed; }; @@ -157,7 +157,7 @@ private static Parser parseBetween( /* * cucumber-expression := ( alternation | optional | parameter | text )* */ - Expression parse(String expression) { + AstNode parse(String expression) { CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); List tokens = tokenizer.tokenize(expression); List ast = new ArrayList<>(); @@ -166,7 +166,7 @@ Expression parse(String expression) { // If configured correctly this will never happen throw new IllegalStateException("Could not parse " + expression); } - return new Expression(ast); + return new AstNode(EXPRESSION_NODE, ast); } private static int parseTokensUntil(List parsers, @@ -225,18 +225,18 @@ private static boolean lookingAt(List expression, int at, Type token) { return expression.get(at).type == token; } - private static List> splitOn(List astNode, T separator) { - List> alternatives = new ArrayList<>(); - List alternative = new ArrayList<>(); - alternatives.add(alternative); - for (T token : astNode) { - if (separator.equals(token)) { + private static List splitAlternatives(List astNode) { + List alternatives = new ArrayList<>(); + List alternative = new ArrayList<>(); + for (AstNode token : astNode) { + if (Ast.ALTERNATIVE_SEPARATOR.equals(token)) { + alternatives.add(new AstNode(ALTERNATIVE_NODE, alternative)); alternative = new ArrayList<>(); - alternatives.add(alternative); } else { alternative.add(token); } } + alternatives.add(new AstNode(ALTERNATIVE_NODE, alternative)); return alternatives; } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 2514a909cb..bec27176f6 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -1,6 +1,6 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.AstNode.Token; +import io.cucumber.cucumberexpressions.Ast.Token; import java.util.ArrayList; import java.util.Arrays; @@ -8,21 +8,21 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ESCAPE; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ESCAPE_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.TEXT; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ESCAPE; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ESCAPE_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE_ESCAPED; final class CucumberExpressionTokenizer { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index adf5c9a9fe..22cd8a68e4 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -1,19 +1,24 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.AstNode.Alternation; -import io.cucumber.cucumberexpressions.AstNode.Optional; -import io.cucumber.cucumberexpressions.AstNode.Parameter; -import io.cucumber.cucumberexpressions.AstNode.Text; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; +import io.cucumber.cucumberexpressions.Ast.AstNode; +import io.cucumber.cucumberexpressions.Ast.Token; import org.junit.jupiter.api.Test; -import java.util.List; - +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATION_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATIVE_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.EXPRESSION_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.OPTIONAL_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; +import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ESCAPE; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; class CucumberExpressionParserTest { @@ -21,217 +26,308 @@ class CucumberExpressionParserTest { @Test void emptyString() { - assertThat(astOf(""), empty()); + assertThat(astOf(""), equalTo( + new AstNode(EXPRESSION_NODE) + )); } @Test void phrase() { - assertThat(astOf("three blind mice"), contains( - node("three", Text.class), - node(" ", Text.class), - node("blind", Text.class), - node(" ", Text.class), - node("mice", Text.class) + assertThat(astOf("three blind mice"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ) )); } @Test void optional() { - assertThat(astOf("(blind)"), contains( - node("blind", Optional.class) + assertThat(astOf("(blind)"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(OPTIONAL_NODE, + new AstNode(TEXT_NODE, new Token("blind", TEXT)) + ) + ) )); } @Test void parameter() { - assertThat(astOf("{string}"), contains( - node("string", Parameter.class) + assertThat(astOf("{string}"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(PARAMETER_NODE, + new AstNode(TEXT_NODE, new Token("string", TEXT)) + ) + ) )); } @Test void anonymousParameter() { - assertThat(astOf("{}"), contains( - node("", Parameter.class) + assertThat(astOf("{}"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(PARAMETER_NODE) + ) )); } @Test void optionalPhrase() { - assertThat(astOf("three (blind) mice"), contains( - node("three", Text.class), - node(" ", Text.class), - node("blind", Optional.class), - node(" ", Text.class), - node("mice", Text.class) + assertThat(astOf("three (blind) mice"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(OPTIONAL_NODE, + new AstNode(TEXT_NODE, new Token("blind", TEXT)) + ), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ) )); } @Test void slash() { - assertThat(astOf("\\"), contains( - node("\\", Text.class) + assertThat(astOf("\\"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("\\", ESCAPE)) + ) )); } @Test void openingBrace() { - assertThat(astOf("{"), contains( - node("{", Text.class) + assertThat(astOf("{"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("{", BEGIN_PARAMETER)) + ) )); } @Test void openingParenthesis() { - assertThat(astOf("("), contains( - node("(", Text.class) + assertThat(astOf("("), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)) + ) )); } @Test void escapedOpeningParenthesis() { - assertThat(astOf("\\("), contains( - node("(", Text.class) + assertThat(astOf("\\("), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)) + ) )); } @Test void escapedOptional() { - assertThat(astOf("\\(blind)"), contains( - node("(", Text.class), - node("blind", Text.class), - node(")", Text.class) + assertThat(astOf("\\(blind)"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), + new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)) + ) )); } @Test void escapedOptionalPhrase() { - assertThat(astOf("three \\(blind) mice"), contains( - node("three", Text.class), - node(" ", Text.class), - node("(", Text.class), - node("blind", Text.class), - node(")", Text.class), - node(" ", Text.class), - node("mice", Text.class) + assertThat(astOf("three \\(blind) mice"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), + new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ) )); } @Test void escapedOptionalFollowedByOptional() { - assertThat(astOf("three \\((very) blind) mice"), contains( - node("three", Text.class), - node(" ", Text.class), - node("(", Text.class), - node("very", Optional.class), - node(" ", Text.class), - node("blind", Text.class), - node(")", Text.class), - node(" ", Text.class), - node("mice", Text.class) + assertThat(astOf("three \\((very) blind) mice"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), + new AstNode(OPTIONAL_NODE, + new AstNode(TEXT_NODE, new Token("very", TEXT)) + ), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ) )); } @Test void optionalContainingEscapedOptional() { - assertThat(astOf("three ((very\\) blind) mice"), contains( - node("three", Text.class), - node(" ", Text.class), - node("(very) blind", Optional.class), - node(" ", Text.class), - node("mice", Text.class) + assertThat(astOf("three ((very\\) blind) mice"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(OPTIONAL_NODE, + new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), + new AstNode(TEXT_NODE, new Token("very", TEXT)), + new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("blind", TEXT)) + ), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ) )); } @Test void alternation() { - assertThat(astOf("mice/rats"), contains( - node("mice - rats", Alternation.class) + assertThat(astOf("mice/rats"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(ALTERNATION_NODE, + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ), + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("rats", TEXT))) + ) + ) )); } @Test void escapedAlternation() { - assertThat(astOf("mice\\/rats"), contains( - node("mice", Text.class), - node("/", Text.class), - node("rats", Text.class) + assertThat(astOf("mice\\/rats"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("mice", TEXT)), + new AstNode(TEXT_NODE, new Token("/", ALTERNATION)), + new AstNode(TEXT_NODE, new Token("rats", TEXT)) + ) )); } @Test void alternationPhrase() { - assertThat(astOf("three hungry/blind mice"), contains( - node("three", Text.class), - node(" ", Text.class), - node("hungry - blind", Alternation.class), - node(" ", Text.class), - node("mice", Text.class) + assertThat(astOf("three hungry/blind mice"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(ALTERNATION_NODE, + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("hungry", TEXT)) + ), + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("blind", TEXT)) + ) + ), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ) )); } @Test void alternationWithWhiteSpace() { - assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), contains( - node(" three hungry - blind mice ", Alternation.class) + assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(ALTERNATION_NODE, + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("hungry", TEXT)) + ), + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)) + ) + ) + + ) )); } @Test void alternationWithUnusedEndOptional() { - assertThat(astOf("three )blind\\ mice/rats"), contains( - node("three", Text.class), - node(" ", Text.class), - node(")blind mice - rats", Alternation.class) + assertThat(astOf("three )blind\\ mice/rats"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(ALTERNATION_NODE, + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), + new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ), + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("rats", TEXT)) + ) + ) + ) )); } @Test void alternationWithUnusedStartOptional() { - assertThat(astOf("three blind\\ mice/rats("), contains( - node("three", Text.class), - node(" ", Text.class), - node("blind mice - rats(", Alternation.class) + assertThat(astOf("three blind\\ mice/rats("), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(ALTERNATION_NODE, + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("mice", TEXT)) + ), + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("rats", TEXT)), + new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)) + ) + ) + ) )); } @Test void alternationFollowedByOptional() { - assertThat(astOf("three blind\\ rat/cat(s)"), contains( - node("three", Text.class), - node(" ", Text.class), - node("blind rat - cats", Alternation.class) - )); - } - - private List astOf(String expression) { - return parser.parse(expression).getNodes(); - } - - private static Matcher node(String expectedExpression, Class type) { - return new TypeSafeDiagnosingMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("("); - description.appendValue(expectedExpression); - description.appendText(","); - description.appendValue(type.getSimpleName()); - description.appendText(")"); - } - - @Override - protected boolean matchesSafely(AstNode node, Description description) { - description.appendText("("); - String expression = node.toString(); - description.appendValue(expression); - description.appendText(","); - description.appendValue(node.getClass().getSimpleName()); - description.appendText(")"); - return expectedExpression.equals(expression) && type.equals(node.getClass()); - } - }; + assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("three", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(ALTERNATION_NODE, + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, new Token("rat", TEXT)) + ), + new AstNode(ALTERNATIVE_NODE, + new AstNode(TEXT_NODE, new Token("cat", TEXT)), + new AstNode(OPTIONAL_NODE, + new AstNode(TEXT_NODE, new Token("s", TEXT)) + ) + ) + ) + ) + )); + } + + private AstNode astOf(String expression) { + return parser.parse(expression); } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index ee1dcc7dda..be29a7869b 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -1,22 +1,22 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.AstNode.Token; +import io.cucumber.cucumberexpressions.Ast.Token; import org.junit.jupiter.api.Test; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.ALTERNATION_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.BEGIN_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.END_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.TEXT; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE; -import static io.cucumber.cucumberexpressions.AstNode.Token.Type.WHITE_SPACE_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE_ESCAPED; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; From 9700f16ef20720a3758d7ba8938c04696008c36e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 16:52:10 +0100 Subject: [PATCH 053/183] rewrite ast to pattern --- cucumber-expressions/go/ast.go | 16 ++- .../go/cucumber_expression.go | 126 +++++++++++++++++- .../go/cucumber_expression_parser.go | 24 ++-- .../go/cucumber_expression_parser_test.go | 90 ++++++------- .../go/cucumber_expression_regexp_test.go | 4 +- .../go/cucumber_expression_test.go | 16 ++- 6 files changed, 211 insertions(+), 65 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index 8cc4257516..d0ead6fe9b 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -1,5 +1,7 @@ package cucumberexpressions +import "strings" + type nodeType int const ( @@ -17,11 +19,20 @@ type astNode struct { token token } +func (node astNode) text() string { + builder := strings.Builder{} + builder.WriteString(node.token.text) + for _, c := range node.nodes { + builder.WriteString(c.text()) + } + return builder.String() +} + // Marker. This way we don't need to model the // the tail end of alternation in the AST: // // alternation := alternative* + ( '/' + alternative* )+ -var alternativeSeparator = astNode{alternativeNode, []astNode{}, token{"/", alternation}} +var alternativeSeparator = astNode{alternativeNode, []astNode{}, token{"/", alternation}} type tokenType int @@ -57,3 +68,6 @@ var beginParameterToken = token{"{", beginParameter} var endParameterToken = token{"}", endParameter} var alternationToken = token{"/", alternation} var escapeToken = token{"\\", escape} + +var nullNode = astNode{textNode, []astNode{}, nullToken} +var nullToken = token{"", text} diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index bf62b055bd..9dca7101fe 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -1,13 +1,18 @@ package cucumberexpressions import ( + "fmt" "reflect" "regexp" + "strings" ) const alternativesMayNotBeEmpty = "Alternative may not be empty: %s" const parameterTypesCanNotBeAlternative = "Parameter types cannot be alternative: %s" const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" +const alternativeMayNotExclusivelyContainOptionals = "Alternative may not exclusively contain optionals: %s" +const couldNotRewrite = "Could not rewrite %s" +const optionalMayNotBeEmpty = "Optional may not be empty: " var escapeRegexp = regexp.MustCompile(`([\\^\[({$.|?*+})\]])`) @@ -20,11 +25,130 @@ type CucumberExpression struct { func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (Expression, error) { result := &CucumberExpression{source: expression, parameterTypeRegistry: parameterTypeRegistry} - pattern := "^$" + + ast, err := parse(expression) + if err != nil { + return nil, err + } + + pattern, err := result.rewriteNodeToRegex(ast) + if err != nil { + return nil, err + } result.treeRegexp = NewTreeRegexp(regexp.MustCompile(pattern)) return result, nil } +func (c *CucumberExpression) rewriteNodeToRegex(node astNode) (string, error) { + switch node.nodeType { + case textNode: + return c.processEscapes(node.token.text), nil + case optionalNode: + err := c.assertNoParameters(node, parameterTypesCanNotBeOptional) + if err != nil { + return "", err + } + err = c.assertNotEmpty(node, optionalMayNotBeEmpty) + if err != nil { + return "", err + } + return c.rewriteNodesToRegex(node.nodes, "", "(?:", ")?") + case alternationNode: + err := c.validateAlternation(node) + if err != nil { + return "", err + } + return c.rewriteNodesToRegex(node.nodes, "|", "(?:", ")") + case alternativeNode: + return c.rewriteNodesToRegex(node.nodes, "", "", "") + case parameterNode: + typeName := node.text() + err := CheckParameterTypeName(typeName) + if err != nil { + return "", err + } + parameterType := c.parameterTypeRegistry.LookupByTypeName(typeName) + if parameterType == nil { + err = NewUndefinedParameterTypeError(typeName) + return "", err + } + c.parameterTypes = append(c.parameterTypes, parameterType) + return buildCaptureRegexp(parameterType.regexps), nil + case expressionNode: + return c.rewriteNodesToRegex(node.nodes, "", "^", "$") + } + return "", NewCucumberExpressionError(fmt.Sprintf(couldNotRewrite, c.source)) +} + +func buildCaptureRegexp(regexps []*regexp.Regexp) string { + if len(regexps) == 1 { + return fmt.Sprintf("(%s)", regexps[0].String()) + } + + captureGroups := make([]string, len(regexps)) + for i, r := range regexps { + captureGroups[i] = fmt.Sprintf("(?:%s)", r.String()) + } + + return fmt.Sprintf("(%s)", strings.Join(captureGroups, "|")) +} + +func (c *CucumberExpression) validateAlternation(alternation astNode) error { + for _, alternative := range alternation.nodes { + if len(alternative.nodes) == 0 { + return NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) + } + err := c.assertNoParameters(alternative, parameterTypesCanNotBeAlternative) + if err != nil { + return err + } + err = c.assertNotEmpty(alternative, alternativeMayNotExclusivelyContainOptionals) + if err != nil { + return err + } + } + return nil +} + +func (c *CucumberExpression) assertNotEmpty(node astNode, message string) error { + for _, node := range node.nodes { + if node.nodeType == textNode { + return nil + } + } + return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) +} + +func (c *CucumberExpression) assertNoParameters(node astNode, message string) error { + for _, node := range node.nodes { + if node.nodeType == parameterNode { + return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) + } + } + return nil +} + +func (c *CucumberExpression) rewriteNodesToRegex(nodes []astNode, delimiter string, prefix string, suffix string) (string, error) { + builder := strings.Builder{} + builder.WriteString(prefix) + for i, node := range nodes { + if i > 0 { + builder.WriteString(delimiter) + } + s, err := c.rewriteNodeToRegex(node) + if err != nil { + return s, err + } + builder.WriteString(s) + } + builder.WriteString(suffix) + return builder.String(), nil +} + +func (c *CucumberExpression) processEscapes(expression string) string { + return escapeRegexp.ReplaceAllString(expression, `\$1`) +} + func (c *CucumberExpression) Match(text string, typeHints ...reflect.Type) ([]*Argument, error) { parameterTypes := make([]*ParameterType, len(c.parameterTypes)) copy(parameterTypes, c.parameterTypes) diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 703beeeea2..958f70a4d2 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -1,7 +1,5 @@ package cucumberexpressions -var nullNode = astNode{textNode, []astNode{}, token{}} - type parser func(expression []token, current int) (int, astNode) /* @@ -36,9 +34,9 @@ var textParser = func(expression []token, current int) (int, astNode) { * parameter := '{' + text* + '}' */ var parameterParser = parseBetween( - optionalNode, - beginOptional, - endOptional, + parameterNode, + beginParameter, + endParameter, textParser, ) @@ -47,9 +45,9 @@ var parameterParser = parseBetween( * option := parameter | text */ var optionalParser = parseBetween( - parameterNode, - beginParameter, - endParameter, + optionalNode, + beginOptional, + endOptional, parameterParser, textParser, ) @@ -72,7 +70,7 @@ func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, p return 0, nullNode } // consumes endToken - return subCurrent + 1 - current, astNode{nodeType, subAst, token{}} + return subCurrent + 1 - current, astNode{nodeType, subAst, nullToken} } } @@ -120,17 +118,17 @@ var alternationParser = func(expression []token, current int) (int, astNode) { alternative := make([]astNode, 0) for _, node := range subAst { if node.nodeType == alternativeSeparator.nodeType { - alternatives = append(alternatives, astNode{alternativeNode, alternative, token{}}) + alternatives = append(alternatives, astNode{alternativeNode, alternative, nullToken}) alternative = make([]astNode, 0) } else { alternative = append(alternative, node) } } - return append(alternatives, astNode{alternativeNode, alternative, token{}}) + return append(alternatives, astNode{alternativeNode, alternative, nullToken}) } // Does not consume right hand boundary token - return consumed, astNode{alternationNode, splitAlternatives(subAst), token{}} + return consumed, astNode{alternationNode, splitAlternatives(subAst), nullToken} } @@ -156,7 +154,7 @@ func parse(expression string) (astNode, error) { return nullNode, NewCucumberExpressionError("Could not parse" + expression) } - return astNode{expressionNode, ast, token{}}, nil + return astNode{expressionNode, ast, nullToken}, nil } func parseTokensUntil(parsers []parser, expresion []token, startAt int, endTokens ...tokenType) (int, []astNode) { diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index 71ca6ec7da..c2009c79f4 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -16,7 +16,7 @@ func TestCucumberExpressionParser(t *testing.T) { assertAst(t, "", astNode{ expressionNode, []astNode{}, - token{}, + nullToken, }) }) @@ -30,7 +30,7 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, }, - token{}, + nullToken, }) }) @@ -42,10 +42,10 @@ func TestCucumberExpressionParser(t *testing.T) { []astNode{ {textNode, []astNode{}, token{"blind", text}}, }, - token{}, + nullToken, }, }, - token{}, + nullToken, }) }) @@ -57,10 +57,10 @@ func TestCucumberExpressionParser(t *testing.T) { []astNode{ {textNode, []astNode{}, token{"string", text}}, }, - token{}, + nullToken, }, }, - token{}, + nullToken, }) }) @@ -70,10 +70,10 @@ func TestCucumberExpressionParser(t *testing.T) { []astNode{ {parameterNode, []astNode{}, - token{}, + nullToken, }, }, - token{}, + nullToken, }) }) @@ -85,11 +85,11 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{" ", whiteSpace}}, {optionalNode, []astNode{ {textNode, []astNode{}, token{"blind", text}}, - }, token{}}, + }, nullToken}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, }, - token{}, + nullToken, }) }) @@ -99,7 +99,7 @@ func TestCucumberExpressionParser(t *testing.T) { []astNode{ {textNode, []astNode{}, token{"\\", escape}}, }, - token{}, + nullToken, }) }) @@ -109,7 +109,7 @@ func TestCucumberExpressionParser(t *testing.T) { []astNode{ {textNode, []astNode{}, token{"{", beginParameter}}, }, - token{}, + nullToken, }) }) @@ -119,7 +119,7 @@ func TestCucumberExpressionParser(t *testing.T) { []astNode{ {textNode, []astNode{}, token{"(", beginOptional}}, }, - token{}, + nullToken, }) }) @@ -129,7 +129,7 @@ func TestCucumberExpressionParser(t *testing.T) { []astNode{ {textNode, []astNode{}, token{"(", beginOptional}}, }, - token{}, + nullToken, }) }) @@ -145,7 +145,7 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, }, - token{}, + nullToken, }) }) @@ -158,14 +158,14 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{"(", beginOptional}}, {optionalNode, []astNode{ {textNode, []astNode{}, token{"very", text}}, - }, token{}}, + }, nullToken}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"blind", text}}, {textNode, []astNode{}, token{")", endOptional}}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, }, - token{}, + nullToken, }) }) @@ -181,11 +181,11 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{")", endOptional}}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"blind", text}}, - }, token{}}, + }, nullToken}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, }, - token{}, + nullToken, }) }) @@ -196,13 +196,13 @@ func TestCucumberExpressionParser(t *testing.T) { {alternationNode, []astNode{ {alternativeNode, []astNode{ {textNode, []astNode{}, token{"mice", text}}, - }, token{}}, + }, nullToken}, {alternativeNode, []astNode{ {textNode, []astNode{}, token{"rats", text}}, - }, token{}}, - }, token{}}, + }, nullToken}, + }, nullToken}, }, - token{}, + nullToken, }) }) @@ -214,7 +214,7 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{"/", alternation}}, {textNode, []astNode{}, token{"rats", text}}, }, - token{}, + nullToken, }) }) @@ -227,15 +227,15 @@ func TestCucumberExpressionParser(t *testing.T) { {alternationNode, []astNode{ {alternativeNode, []astNode{ {textNode, []astNode{}, token{"hungry", text}}, - }, token{}}, + }, nullToken}, {alternativeNode, []astNode{ {textNode, []astNode{}, token{"blind", text}}, - }, token{}}, - }, token{}}, + }, nullToken}, + }, nullToken}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, }, - token{}, + nullToken, }) }) @@ -249,16 +249,16 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{"three", text}}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"hungry", text}}, - }, token{}}, + }, nullToken}, {alternativeNode, []astNode{ {textNode, []astNode{}, token{"blind", text}}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, {textNode, []astNode{}, token{" ", whiteSpace}}, - }, token{}}, - }, token{}}, + }, nullToken}, + }, nullToken}, }, - token{}, + nullToken, }) }) @@ -274,13 +274,13 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{"blind", text}}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, - }, token{}}, + }, nullToken}, {alternativeNode, []astNode{ {textNode, []astNode{}, token{"rats", text}}, - }, token{}}, - }, token{}}, + }, nullToken}, + }, nullToken}, }, - token{}, + nullToken, }) }) @@ -295,14 +295,14 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{"blind", text}}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"mice", text}}, - }, token{}}, + }, nullToken}, {alternativeNode, []astNode{ {textNode, []astNode{}, token{"rats", text}}, {textNode, []astNode{}, token{"(", beginOptional}}, - }, token{}}, - }, token{}}, + }, nullToken}, + }, nullToken}, }, - token{}, + nullToken, }) }) @@ -317,16 +317,16 @@ func TestCucumberExpressionParser(t *testing.T) { {textNode, []astNode{}, token{"blind", text}}, {textNode, []astNode{}, token{" ", whiteSpace}}, {textNode, []astNode{}, token{"rat", text}}, - }, token{}}, + }, nullToken}, {alternativeNode, []astNode{ {textNode, []astNode{}, token{"cat", text}}, {optionalNode, []astNode{ {textNode, []astNode{}, token{"s", text}}, - }, token{}}, - }, token{}}, - }, token{}}, + }, nullToken}, + }, nullToken}, + }, nullToken}, }, - token{}, + nullToken, }) }) diff --git a/cucumber-expressions/go/cucumber_expression_regexp_test.go b/cucumber-expressions/go/cucumber_expression_regexp_test.go index 1202a6410d..c3aa09733a 100644 --- a/cucumber-expressions/go/cucumber_expression_regexp_test.go +++ b/cucumber-expressions/go/cucumber_expression_regexp_test.go @@ -32,8 +32,8 @@ func TestCucumberExpressionRegExpTranslation(t *testing.T) { t.Run("translates alternation with optional words", func(t *testing.T) { assertRegexp( t, - "the (test )chat/call/email interactions are visible", - "^the (?:test )?(?:chat|call|email) interactions are visible$", + "the (test )chat/(test )call/(test )email interactions are visible", + "^the (?:(?:test )?chat|(?:test )?call|(?:test )?email) interactions are visible$", ) }) diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 6b37700cb0..3802c40623 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -41,7 +41,12 @@ func TestCucumberExpression(t *testing.T) { t.Run("matches optional before alternation", func(t *testing.T) { require.Equal( t, - MatchCucumberExpression(t, "three (brown )mice/rats", "three brown rats"), + MatchCucumberExpression(t, "three (brown )mice/rats", "three rats"), + []interface{}{}, + ) + require.Equal( + t, + MatchCucumberExpression(t, "three (brown )mice/rats", "three brown mice"), []interface{}{}, ) }) @@ -65,7 +70,7 @@ func TestCucumberExpression(t *testing.T) { parameterTypeRegistry := NewParameterTypeRegistry() _, err := NewCucumberExpression("three (brown)/black mice", parameterTypeRegistry) require.Error(t, err) - require.Equal(t, "Alternative may not be empty: three (brown)/black mice", err.Error()) + require.Equal(t, "Alternative may not exclusively contain optionals: three (brown)/black mice", err.Error()) }) t.Run("matches double quoted string", func(t *testing.T) { @@ -197,7 +202,12 @@ func TestCucumberExpression(t *testing.T) { t.Run("matches doubly escaped slash", func(t *testing.T) { require.Equal( t, - MatchCucumberExpression(t, "12\\\\/2020", `12\/2020`), + MatchCucumberExpression(t, "12\\\\/2020", `12\`), + []interface{}{}, + ) + require.Equal( + t, + MatchCucumberExpression(t, "12\\\\/2020", `2020`), []interface{}{}, ) }) From 6aa1d4804c1034d6b9dc9a2f6f42c9f355144f74 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 16:52:31 +0100 Subject: [PATCH 054/183] clean up --- .../main/java/io/cucumber/cucumberexpressions/Ast.java | 8 +++++++- .../cucumber/cucumberexpressions/CucumberExpression.java | 8 +++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 49b91c3f6c..296783cc92 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -6,6 +6,7 @@ import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATIVE_NODE; import static io.cucumber.cucumberexpressions.Ast.Token.ALTERNATION; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.joining; final class Ast { // Marker. This way we don't need to model the @@ -57,7 +58,12 @@ Type getType() { } String getText() { - return token.text; + if(token != null) + return token.text; + + return getNodes().stream() + .map(AstNode::getText) + .collect(joining()); } @Override diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 83c893b69a..0ec0f910f0 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -55,9 +55,7 @@ private String rewriteToRegex(AstNode node) { .map(this::rewriteToRegex) .collect(joining()); case PARAMETER_NODE: - String name = node.getNodes().stream() - .map(AstNode::getText) - .collect(joining()); + String name = node.getText(); ParameterType.checkParameterTypeName(name); ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); if (parameterType == null) { @@ -102,12 +100,12 @@ private void validateAlternation(AstNode alternation) { } } - private void assertNoParameters(AstNode node, String parameterTypesCannotBeAlternative) { + private void assertNoParameters(AstNode node, String message) { boolean hasParameter = node.getNodes().stream() .map(AstNode::getType) .anyMatch(type -> type == PARAMETER_NODE); if (hasParameter) { - throw new CucumberExpressionException(parameterTypesCannotBeAlternative + source); + throw new CucumberExpressionException(message + source); } } From 97e7910d4d8e0f80cb1b93168425819c857ec592 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 17:15:00 +0100 Subject: [PATCH 055/183] Add missing tests --- .../go/cucumber_expression.go | 2 +- .../go/cucumber_expression_test.go | 61 +++++++++++++++++++ .../CucumberExpressionTest.java | 2 +- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 9dca7101fe..f8915dfa91 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -12,7 +12,7 @@ const parameterTypesCanNotBeAlternative = "Parameter types cannot be alternative const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" const alternativeMayNotExclusivelyContainOptionals = "Alternative may not exclusively contain optionals: %s" const couldNotRewrite = "Could not rewrite %s" -const optionalMayNotBeEmpty = "Optional may not be empty: " +const optionalMayNotBeEmpty = "Optional may not be empty: %s" var escapeRegexp = regexp.MustCompile(`([\\^\[({$.|?*+})\]])`) diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 3802c40623..773de065db 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -51,6 +51,39 @@ func TestCucumberExpression(t *testing.T) { ) }) + t.Run("matches optional in alternation", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "{int} rat(s)/mice/mouse", "3 rats"), + []interface{}{3}, + ) + require.Equal( + t, + MatchCucumberExpression(t, "{int} rat(s)/mice/mouse", "2 mice"), + []interface{}{2}, + ) + require.Equal( + t, + MatchCucumberExpression(t, "{int} rat(s)/mice/mouse", "1 mouse"), + []interface{}{1}, + ) + + }) + + t.Run("matches optional before alternation with regex characters", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "I wait {int} second(s)./second(s)?", "I wait 2 seconds?"), + []interface{}{2}, + ) + require.Equal( + t, + MatchCucumberExpression(t, "I wait {int} second(s)./second(s)?", "I wait 1 second?"), + []interface{}{1}, + ) + + }) + t.Run("matches alternation in optional as text", func(t *testing.T) { require.Equal( t, @@ -59,6 +92,13 @@ func TestCucumberExpression(t *testing.T) { ) }) + t.Run("does not allow empty optional", func(t *testing.T) { + parameterTypeRegistry := NewParameterTypeRegistry() + _, err := NewCucumberExpression("three () mice", parameterTypeRegistry) + require.Error(t, err) + require.Equal(t, "Optional may not be empty: three () mice", err.Error()) + }) + t.Run("does not allow alternation with empty alternatives", func(t *testing.T) { parameterTypeRegistry := NewParameterTypeRegistry() _, err := NewCucumberExpression("three brown//black mice", parameterTypeRegistry) @@ -182,6 +222,16 @@ func TestCucumberExpression(t *testing.T) { MatchCucumberExpression(t, "three \\(exceptionally) \\{string} mice", `three (exceptionally) {string} mice`), []interface{}{}, ) + require.Equal( + t, + MatchCucumberExpression(t, "three \\((exceptionally)) \\{{string}} mice", `three (exceptionally) {"blind"} mice`), + []interface{}{"blind"}, + ) + require.Equal( + t, + MatchCucumberExpression(t, "three ((exceptionally\\)) {{string\\}} mice", `three (exceptionally) "blind" mice`), + []interface{}{"\"blind\""}, + ) }) t.Run("matches doubly escaped parenthesis", func(t *testing.T) { @@ -417,6 +467,17 @@ func TestCucumberExpression(t *testing.T) { func MatchCucumberExpression(t *testing.T, expr string, text string, typeHints ...reflect.Type) []interface{} { parameterTypeRegistry := NewParameterTypeRegistry() + parameterType1, err := NewParameterType( + "{string}", + []*regexp.Regexp{regexp.MustCompile(`".*"`)}, + "string", + nil, + true, + false, + false, + ) + require.NoError(t, err) + require.NoError(t, parameterTypeRegistry.DefineParameterType(parameterType1)) expression, err := NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) args, err := expression.Match(text, typeHints...) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 0eafa47454..106383d85f 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -56,7 +56,7 @@ public void matches_optional_in_alternation() { @Test public void matches_optional_before_alternation_with_regex_characters() { - assertEquals(singletonList(2), match("I wait {int} second(s)./second(s)?", "I wait 2 second?")); + assertEquals(singletonList(2), match("I wait {int} second(s)./second(s)?", "I wait 2 seconds?")); assertEquals(singletonList(1), match("I wait {int} second(s)./second(s)?", "I wait 1 second.")); } From d909c52cb8ce20fe626d6d1b5cc57536eeb1bf78 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 17:49:16 +0100 Subject: [PATCH 056/183] Clean up --- .../go/cucumber_expression.go | 150 ++++++++++-------- .../CucumberExpression.java | 107 +++++++------ 2 files changed, 144 insertions(+), 113 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index f8915dfa91..4455831f62 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -44,88 +44,88 @@ func (c *CucumberExpression) rewriteNodeToRegex(node astNode) (string, error) { case textNode: return c.processEscapes(node.token.text), nil case optionalNode: - err := c.assertNoParameters(node, parameterTypesCanNotBeOptional) - if err != nil { - return "", err - } - err = c.assertNotEmpty(node, optionalMayNotBeEmpty) - if err != nil { - return "", err - } - return c.rewriteNodesToRegex(node.nodes, "", "(?:", ")?") + return c.rewriteOptional(node) case alternationNode: - err := c.validateAlternation(node) - if err != nil { - return "", err - } - return c.rewriteNodesToRegex(node.nodes, "|", "(?:", ")") + return c.rewriteAlternation(node) case alternativeNode: - return c.rewriteNodesToRegex(node.nodes, "", "", "") + return c.rewriteAlternative(node) case parameterNode: - typeName := node.text() - err := CheckParameterTypeName(typeName) - if err != nil { - return "", err - } - parameterType := c.parameterTypeRegistry.LookupByTypeName(typeName) - if parameterType == nil { - err = NewUndefinedParameterTypeError(typeName) - return "", err - } - c.parameterTypes = append(c.parameterTypes, parameterType) - return buildCaptureRegexp(parameterType.regexps), nil + return c.rewriteParameter(node) case expressionNode: - return c.rewriteNodesToRegex(node.nodes, "", "^", "$") + return c.rewriteExpression(node) + default: + return "", NewCucumberExpressionError(fmt.Sprintf(couldNotRewrite, c.source)) } - return "", NewCucumberExpressionError(fmt.Sprintf(couldNotRewrite, c.source)) } -func buildCaptureRegexp(regexps []*regexp.Regexp) string { - if len(regexps) == 1 { - return fmt.Sprintf("(%s)", regexps[0].String()) - } +func (c *CucumberExpression) processEscapes(expression string) string { + return escapeRegexp.ReplaceAllString(expression, `\$1`) +} - captureGroups := make([]string, len(regexps)) - for i, r := range regexps { - captureGroups[i] = fmt.Sprintf("(?:%s)", r.String()) +func (c *CucumberExpression) rewriteOptional(node astNode) (string, error) { + err := c.assertNoParameters(node, parameterTypesCanNotBeOptional) + if err != nil { + return "", err } - - return fmt.Sprintf("(%s)", strings.Join(captureGroups, "|")) + err = c.assertNotEmpty(node, optionalMayNotBeEmpty) + if err != nil { + return "", err + } + return c.rewriteNodesToRegex(node.nodes, "", "(?:", ")?") } -func (c *CucumberExpression) validateAlternation(alternation astNode) error { - for _, alternative := range alternation.nodes { +func (c *CucumberExpression) rewriteAlternation(node astNode) (string, error) { + // Make sure the alternative parts aren't empty and don't contain parameter types + for _, alternative := range node.nodes { if len(alternative.nodes) == 0 { - return NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) + return "", NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) } err := c.assertNoParameters(alternative, parameterTypesCanNotBeAlternative) if err != nil { - return err + return "", err } err = c.assertNotEmpty(alternative, alternativeMayNotExclusivelyContainOptionals) if err != nil { - return err + return "", err } } - return nil + return c.rewriteNodesToRegex(node.nodes, "|", "(?:", ")") } -func (c *CucumberExpression) assertNotEmpty(node astNode, message string) error { - for _, node := range node.nodes { - if node.nodeType == textNode { - return nil - } - } - return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) +func (c *CucumberExpression) rewriteAlternative(node astNode) (string, error) { + return c.rewriteNodesToRegex(node.nodes, "", "", "") } -func (c *CucumberExpression) assertNoParameters(node astNode, message string) error { - for _, node := range node.nodes { - if node.nodeType == parameterNode { - return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) +func (c *CucumberExpression) rewriteParameter(node astNode) (string, error) { + buildCaptureRegexp := func(regexps []*regexp.Regexp) string { + if len(regexps) == 1 { + return fmt.Sprintf("(%s)", regexps[0].String()) + } + + captureGroups := make([]string, len(regexps)) + for i, r := range regexps { + captureGroups[i] = fmt.Sprintf("(?:%s)", r.String()) } + + return fmt.Sprintf("(%s)", strings.Join(captureGroups, "|")) } - return nil + + typeName := node.text() + err := CheckParameterTypeName(typeName) + if err != nil { + return "", err + } + parameterType := c.parameterTypeRegistry.LookupByTypeName(typeName) + if parameterType == nil { + err = NewUndefinedParameterTypeError(typeName) + return "", err + } + c.parameterTypes = append(c.parameterTypes, parameterType) + return buildCaptureRegexp(parameterType.regexps), nil +} + +func (c *CucumberExpression) rewriteExpression(node astNode) (string, error) { + return c.rewriteNodesToRegex(node.nodes, "", "^", "$") } func (c *CucumberExpression) rewriteNodesToRegex(nodes []astNode, delimiter string, prefix string, suffix string) (string, error) { @@ -145,11 +145,33 @@ func (c *CucumberExpression) rewriteNodesToRegex(nodes []astNode, delimiter stri return builder.String(), nil } -func (c *CucumberExpression) processEscapes(expression string) string { - return escapeRegexp.ReplaceAllString(expression, `\$1`) +func (c *CucumberExpression) assertNotEmpty(node astNode, message string) error { + for _, node := range node.nodes { + if node.nodeType == textNode { + return nil + } + } + return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) +} + +func (c *CucumberExpression) assertNoParameters(node astNode, message string) error { + for _, node := range node.nodes { + if node.nodeType == parameterNode { + return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) + } + } + return nil } func (c *CucumberExpression) Match(text string, typeHints ...reflect.Type) ([]*Argument, error) { + hintOrDefault := func(i int, typeHints ...reflect.Type) reflect.Type { + typeHint := reflect.TypeOf("") + if i < len(typeHints) { + typeHint = typeHints[i] + } + return typeHint + } + parameterTypes := make([]*ParameterType, len(c.parameterTypes)) copy(parameterTypes, c.parameterTypes) for i := 0; i < len(parameterTypes); i++ { @@ -165,14 +187,6 @@ func (c *CucumberExpression) Match(text string, typeHints ...reflect.Type) ([]*A return BuildArguments(c.treeRegexp, text, parameterTypes), nil } -func hintOrDefault(i int, typeHints ...reflect.Type) reflect.Type { - typeHint := reflect.TypeOf("") - if i < len(typeHints) { - typeHint = typeHints[i] - } - return typeHint -} - func (c *CucumberExpression) Regexp() *regexp.Regexp { return c.treeRegexp.Regexp() } @@ -181,9 +195,9 @@ func (c *CucumberExpression) Source() string { return c.source } -func (r *CucumberExpression) objectMapperTransformer(typeHint reflect.Type) func(args ...*string) interface{} { +func (c *CucumberExpression) objectMapperTransformer(typeHint reflect.Type) func(args ...*string) interface{} { return func(args ...*string) interface{} { - i, err := r.parameterTypeRegistry.defaultTransformer.Transform(*args[0], typeHint) + i, err := c.parameterTypeRegistry.defaultTransformer.Transform(*args[0], typeHint) if err != nil { panic(err) } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 0ec0f910f0..c730f8e825 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -38,66 +38,87 @@ public final class CucumberExpression implements Expression { private String rewriteToRegex(AstNode node) { switch (node.getType()) { + case TEXT_NODE: + return escapeRegex(node.getText()); case OPTIONAL_NODE: - assertNoParameters(node, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - assertNotEmpty(node, OPTIONAL_MAY_NOT_BE_EMPTY); - return node.getNodes().stream() - .map(this::rewriteToRegex) - .collect(joining("", "(?:", ")?")); + return rewriteOptional(node); case ALTERNATION_NODE: - validateAlternation(node); - return node.getNodes() - .stream() - .map(this::rewriteToRegex) - .collect(joining("|", "(?:", ")")); + return rewriteAlternation(node); case ALTERNATIVE_NODE: - return node.getNodes().stream() - .map(this::rewriteToRegex) - .collect(joining()); + return rewriteAlternative(node); case PARAMETER_NODE: - String name = node.getText(); - ParameterType.checkParameterTypeName(name); - ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); - if (parameterType == null) { - throw new UndefinedParameterTypeException(name); - } - parameterTypes.add(parameterType); - List regexps = parameterType.getRegexps(); - if (regexps.size() == 1) { - return "(" + regexps.get(0) + ")"; - } - return regexps.stream() - .collect(joining(")|(?:", "((?:", "))")); - case TEXT_NODE: - return escapeRegex(node.getText()); + return rewriteParameter(node); case EXPRESSION_NODE: - return node.getNodes().stream() - .map(this::rewriteToRegex) - .collect(joining("", "^", "$")); + return rewriteExpression(node); default: throw new IllegalArgumentException(node.getType().name()); } } - private void assertNotEmpty(AstNode node, String message) { - boolean hasTextNode = node.getNodes() - .stream() - .map(AstNode::getType) - .anyMatch(type -> type == TEXT_NODE); - if (!hasTextNode) { - throw new CucumberExpressionException(message + source); - } + private static String escapeRegex(String text) { + return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); + } + + private String rewriteOptional(AstNode node) { + assertNoParameters(node, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); + assertNotEmpty(node, OPTIONAL_MAY_NOT_BE_EMPTY); + return node.getNodes().stream() + .map(this::rewriteToRegex) + .collect(joining("", "(?:", ")?")); } - private void validateAlternation(AstNode alternation) { + + private String rewriteAlternation(AstNode node) { // Make sure the alternative parts aren't empty and don't contain parameter types - for (AstNode alternative : alternation.getNodes()) { + for (AstNode alternative : node.getNodes()) { if (alternative.getNodes().isEmpty()) { throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); } assertNoParameters(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); assertNotEmpty(alternative, ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS); } + return node.getNodes() + .stream() + .map(this::rewriteToRegex) + .collect(joining("|", "(?:", ")")); + } + + private String rewriteAlternative(AstNode node) { + return node.getNodes().stream() + .map(this::rewriteToRegex) + .collect(joining()); + } + + private String rewriteParameter(AstNode node) { + String name = node.getText(); + ParameterType.checkParameterTypeName(name); + ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); + if (parameterType == null) { + throw new UndefinedParameterTypeException(name); + } + parameterTypes.add(parameterType); + List regexps = parameterType.getRegexps(); + if (regexps.size() == 1) { + return "(" + regexps.get(0) + ")"; + } + return regexps.stream() + .collect(joining(")|(?:", "((?:", "))")); + } + + private String rewriteExpression(AstNode node) { + return node.getNodes().stream() + .map(this::rewriteToRegex) + .collect(joining("", "^", "$")); + } + + private void assertNotEmpty(AstNode node, String message) { + boolean hasTextNode = node.getNodes() + .stream() + .map(AstNode::getType) + .anyMatch(type -> type == TEXT_NODE); + if (!hasTextNode) { + throw new CucumberExpressionException(message + source); + } } private void assertNoParameters(AstNode node, String message) { @@ -109,10 +130,6 @@ private void assertNoParameters(AstNode node, String message) { } } - private static String escapeRegex(String text) { - return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); - } - @Override public List> match(String text, Type... typeHints) { final Group group = treeRegexp.match(text); From d5ce42df7b104a3c85fad9b69362802a6be5a805 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 18:00:21 +0100 Subject: [PATCH 057/183] rewrite --> compile --- cucumber-expressions/go/cucumber_expression.go | 16 ++++++++-------- .../cucumberexpressions/CucumberExpression.java | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 4455831f62..6af55be02b 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -31,7 +31,7 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return nil, err } - pattern, err := result.rewriteNodeToRegex(ast) + pattern, err := result.compileNodeToRegex(ast) if err != nil { return nil, err } @@ -39,7 +39,7 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return result, nil } -func (c *CucumberExpression) rewriteNodeToRegex(node astNode) (string, error) { +func (c *CucumberExpression) compileNodeToRegex(node astNode) (string, error) { switch node.nodeType { case textNode: return c.processEscapes(node.token.text), nil @@ -71,7 +71,7 @@ func (c *CucumberExpression) rewriteOptional(node astNode) (string, error) { if err != nil { return "", err } - return c.rewriteNodesToRegex(node.nodes, "", "(?:", ")?") + return c.compileNodesToRegex(node.nodes, "", "(?:", ")?") } func (c *CucumberExpression) rewriteAlternation(node astNode) (string, error) { @@ -89,11 +89,11 @@ func (c *CucumberExpression) rewriteAlternation(node astNode) (string, error) { return "", err } } - return c.rewriteNodesToRegex(node.nodes, "|", "(?:", ")") + return c.compileNodesToRegex(node.nodes, "|", "(?:", ")") } func (c *CucumberExpression) rewriteAlternative(node astNode) (string, error) { - return c.rewriteNodesToRegex(node.nodes, "", "", "") + return c.compileNodesToRegex(node.nodes, "", "", "") } func (c *CucumberExpression) rewriteParameter(node astNode) (string, error) { @@ -125,17 +125,17 @@ func (c *CucumberExpression) rewriteParameter(node astNode) (string, error) { } func (c *CucumberExpression) rewriteExpression(node astNode) (string, error) { - return c.rewriteNodesToRegex(node.nodes, "", "^", "$") + return c.compileNodesToRegex(node.nodes, "", "^", "$") } -func (c *CucumberExpression) rewriteNodesToRegex(nodes []astNode, delimiter string, prefix string, suffix string) (string, error) { +func (c *CucumberExpression) compileNodesToRegex(nodes []astNode, delimiter string, prefix string, suffix string) (string, error) { builder := strings.Builder{} builder.WriteString(prefix) for i, node := range nodes { if i > 0 { builder.WriteString(delimiter) } - s, err := c.rewriteNodeToRegex(node) + s, err := c.compileNodeToRegex(node) if err != nil { return s, err } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index c730f8e825..97be50df04 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -32,11 +32,11 @@ public final class CucumberExpression implements Expression { CucumberExpressionParser parser = new CucumberExpressionParser(); AstNode ast = parser.parse(expression); - String pattern = rewriteToRegex(ast); + String pattern = compileToRegex(ast); treeRegexp = new TreeRegexp(pattern); } - private String rewriteToRegex(AstNode node) { + private String compileToRegex(AstNode node) { switch (node.getType()) { case TEXT_NODE: return escapeRegex(node.getText()); @@ -63,7 +63,7 @@ private String rewriteOptional(AstNode node) { assertNoParameters(node, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); assertNotEmpty(node, OPTIONAL_MAY_NOT_BE_EMPTY); return node.getNodes().stream() - .map(this::rewriteToRegex) + .map(this::compileToRegex) .collect(joining("", "(?:", ")?")); } @@ -79,13 +79,13 @@ private String rewriteAlternation(AstNode node) { } return node.getNodes() .stream() - .map(this::rewriteToRegex) + .map(this::compileToRegex) .collect(joining("|", "(?:", ")")); } private String rewriteAlternative(AstNode node) { return node.getNodes().stream() - .map(this::rewriteToRegex) + .map(this::compileToRegex) .collect(joining()); } @@ -107,7 +107,7 @@ private String rewriteParameter(AstNode node) { private String rewriteExpression(AstNode node) { return node.getNodes().stream() - .map(this::rewriteToRegex) + .map(this::compileToRegex) .collect(joining("", "^", "$")); } From d83aa486e100a2513aca4cb86a57271c61bfcee7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 18:00:51 +0100 Subject: [PATCH 058/183] Revert "rewrite --> compile" This reverts commit 20fc308c --- cucumber-expressions/go/cucumber_expression.go | 16 ++++++++-------- .../cucumberexpressions/CucumberExpression.java | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 6af55be02b..4455831f62 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -31,7 +31,7 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return nil, err } - pattern, err := result.compileNodeToRegex(ast) + pattern, err := result.rewriteNodeToRegex(ast) if err != nil { return nil, err } @@ -39,7 +39,7 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return result, nil } -func (c *CucumberExpression) compileNodeToRegex(node astNode) (string, error) { +func (c *CucumberExpression) rewriteNodeToRegex(node astNode) (string, error) { switch node.nodeType { case textNode: return c.processEscapes(node.token.text), nil @@ -71,7 +71,7 @@ func (c *CucumberExpression) rewriteOptional(node astNode) (string, error) { if err != nil { return "", err } - return c.compileNodesToRegex(node.nodes, "", "(?:", ")?") + return c.rewriteNodesToRegex(node.nodes, "", "(?:", ")?") } func (c *CucumberExpression) rewriteAlternation(node astNode) (string, error) { @@ -89,11 +89,11 @@ func (c *CucumberExpression) rewriteAlternation(node astNode) (string, error) { return "", err } } - return c.compileNodesToRegex(node.nodes, "|", "(?:", ")") + return c.rewriteNodesToRegex(node.nodes, "|", "(?:", ")") } func (c *CucumberExpression) rewriteAlternative(node astNode) (string, error) { - return c.compileNodesToRegex(node.nodes, "", "", "") + return c.rewriteNodesToRegex(node.nodes, "", "", "") } func (c *CucumberExpression) rewriteParameter(node astNode) (string, error) { @@ -125,17 +125,17 @@ func (c *CucumberExpression) rewriteParameter(node astNode) (string, error) { } func (c *CucumberExpression) rewriteExpression(node astNode) (string, error) { - return c.compileNodesToRegex(node.nodes, "", "^", "$") + return c.rewriteNodesToRegex(node.nodes, "", "^", "$") } -func (c *CucumberExpression) compileNodesToRegex(nodes []astNode, delimiter string, prefix string, suffix string) (string, error) { +func (c *CucumberExpression) rewriteNodesToRegex(nodes []astNode, delimiter string, prefix string, suffix string) (string, error) { builder := strings.Builder{} builder.WriteString(prefix) for i, node := range nodes { if i > 0 { builder.WriteString(delimiter) } - s, err := c.compileNodeToRegex(node) + s, err := c.rewriteNodeToRegex(node) if err != nil { return s, err } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 97be50df04..c730f8e825 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -32,11 +32,11 @@ public final class CucumberExpression implements Expression { CucumberExpressionParser parser = new CucumberExpressionParser(); AstNode ast = parser.parse(expression); - String pattern = compileToRegex(ast); + String pattern = rewriteToRegex(ast); treeRegexp = new TreeRegexp(pattern); } - private String compileToRegex(AstNode node) { + private String rewriteToRegex(AstNode node) { switch (node.getType()) { case TEXT_NODE: return escapeRegex(node.getText()); @@ -63,7 +63,7 @@ private String rewriteOptional(AstNode node) { assertNoParameters(node, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); assertNotEmpty(node, OPTIONAL_MAY_NOT_BE_EMPTY); return node.getNodes().stream() - .map(this::compileToRegex) + .map(this::rewriteToRegex) .collect(joining("", "(?:", ")?")); } @@ -79,13 +79,13 @@ private String rewriteAlternation(AstNode node) { } return node.getNodes() .stream() - .map(this::compileToRegex) + .map(this::rewriteToRegex) .collect(joining("|", "(?:", ")")); } private String rewriteAlternative(AstNode node) { return node.getNodes().stream() - .map(this::compileToRegex) + .map(this::rewriteToRegex) .collect(joining()); } @@ -107,7 +107,7 @@ private String rewriteParameter(AstNode node) { private String rewriteExpression(AstNode node) { return node.getNodes().stream() - .map(this::compileToRegex) + .map(this::rewriteToRegex) .collect(joining("", "^", "$")); } From 5b7c150127ca6ce7ada9ca855af154ed8e77f570 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 18:31:23 +0100 Subject: [PATCH 059/183] Clean up dead code --- cucumber-expressions/go/cucumber_expression_parser.go | 6 +++--- .../go/cucumber_expression_parser_test.go | 11 +++++++++++ .../cucumberexpressions/CucumberExpressionParser.java | 6 +++--- .../CucumberExpressionParserTest.java | 10 ++++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 958f70a4d2..8e3a6de383 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -63,9 +63,6 @@ func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, p subCurrent += consumed // endToken not found - if lookingAt(expression, subCurrent, endOfLine) { - return 0, nullNode - } if !lookingAt(expression, subCurrent, endToken) { return 0, nullNode } @@ -167,6 +164,8 @@ func parseTokensUntil(parsers []parser, expresion []token, startAt int, endToken } consumed, node := parseToken(parsers, expresion, current) if consumed == 0 { + // If configured correctly this will never happen + // Keep to avoid infinite loops break } current += consumed @@ -183,6 +182,7 @@ func parseToken(parsers []parser, expresion []token, startAt int) (int, astNode) return consumed, node } } + // If configured correctly this will never happen return 0, nullNode } diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index c2009c79f4..342219f898 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -113,6 +113,17 @@ func TestCucumberExpressionParser(t *testing.T) { }) }) + t.Run("unfinished parameter", func(t *testing.T) { + assertAst(t, "{string", astNode{ + expressionNode, + []astNode{ + {textNode, []astNode{}, token{"{", beginParameter}}, + {textNode, []astNode{}, token{"string",text}}, + }, + nullToken, + }) + }) + t.Run("opening parenthesis", func(t *testing.T) { assertAst(t, "(", astNode{ expressionNode, diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index fb7dc9e24f..a89cbfb89d 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -98,9 +98,6 @@ private static Parser parseBetween( subCurrent += consumed; // endToken not found - if (lookingAt(expression, subCurrent, END_OF_LINE)) { - return 0; - } if (!lookingAt(expression, subCurrent, endToken)) { return 0; } @@ -183,6 +180,8 @@ private static int parseTokensUntil(List parsers, int consumed = parseToken(parsers, ast, expression, current); if (consumed == 0) { + // If configured correctly this will never happen + // Keep to avoid infinite loops break; } current += consumed; @@ -200,6 +199,7 @@ private static int parseToken(List parsers, return consumed; } } + // If configured correctly this will never happen return 0; } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 22cd8a68e4..dcaf89358a 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -108,6 +108,16 @@ void openingBrace() { )); } + @Test + void unfinishedParameter() { + assertThat(astOf("{string"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, new Token("{", BEGIN_PARAMETER)), + new AstNode(TEXT_NODE, new Token("string", TEXT)) + ) + )); + } + @Test void openingParenthesis() { assertThat(astOf("("), equalTo( From 520667870fca9971d7758bf7f1828e95fad7e7fb Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 18:36:16 +0100 Subject: [PATCH 060/183] Clean up dead code --- .../go/cucumber_expression_parser.go | 14 +++++--------- .../CucumberExpressionParser.java | 13 +++++-------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 8e3a6de383..154c6dbfff 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -136,7 +136,6 @@ var cucumberExpressionParsers = []parser{ textParser, } - /* * cucumber-expression := ( alternation | optional | parameter | text )* */ @@ -197,14 +196,11 @@ func lookingAtAny(expression []token, at int, tokens ...tokenType) bool { func lookingAt(expression []token, at int, token tokenType) bool { size := len(expression) - if at < 0 || at >= size { - if token == startOfLine { - return at < 0 - } - if token == endOfLine { - return at >= size - } - return false + if at < 0 { + return token == startOfLine + } + if at >= size { + return token == endOfLine } return expression[at].tokenType == token } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index a89cbfb89d..ee27425eae 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -213,14 +213,11 @@ private static boolean lookingAt(List expression, int at, Type... tokens) } private static boolean lookingAt(List expression, int at, Type token) { - if (at < 0 || at >= expression.size()) { - if (token == START_OF_LINE) { - return at < 0; - } - if (token == END_OF_LINE) { - return at >= expression.size(); - } - return false; + if (at < 0) { + return token == START_OF_LINE; + } + if (at >= expression.size()) { + return token == END_OF_LINE; } return expression.get(at).type == token; } From 45c80942de0f16cd5367e1b58d2ec8940c40bbf7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 19:17:36 +0100 Subject: [PATCH 061/183] Add regex production rules --- cucumber-expressions/README.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index c5b49f483c..6551f55001 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -1,8 +1,10 @@ -See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) for more details. - +See [website docs](https://cucumber.io/docs/cucumber/cucumber-expressions/) +for more details. ## Grammar ## +A Cucumber Expression has the following AST: + ``` cucumber-expression := ( alternation | optional | parameter | text )* alternation := (?<=boundary) + alternative* + ( '/' + alternative* )+ + (?=boundary) @@ -23,10 +25,34 @@ Note: * All escaped tokens (tokens starting with a backslash) are rewritten to their unescaped equivalent after parsing. +### Production Rules + +The AST can be rewritten into a a regular expression by the following production +rules: + +``` +cucumber-expression -> '^' + rewrite(node[0]) + ... + rewrite(node[n-1]) + '$' +alternation -> '(?:' + rewrite(node[0]) +'|' + ... +'|' + rewerite(node[n-1]) + ')' +alternative -> rewrite(node[0]) + ... + rewrite(node[n-1]) +optional -> '(?:' + rewrite(node[0]) + ... + rewrite(node[n-1]) + ')?' +parameter -> { + parameter_name := node[0].text + ... + node[n-1].text + parameter_pattern := parameter_type_registry[parameter_name] + '((?:' + parameter_pattern[0] + ')|(?:' ... + ')|(?:' + parameter_pattern[n-1] + '))' +} +text -> { + escape_regex := escape '^', `\`, `[` ,`(`, `{`, `$`, `.`, `|`, `?`, `*`, `+`, + `}`, `)` and `]` + escape_regex(token.text) +} +``` + ## Acknowledgements The Cucumber Expression syntax is inspired by similar expression syntaxes in -other BDD tools, such as [Turnip](https://github.com/jnicklas/turnip), [Behat](https://github.com/Behat/Behat) and [Behave](https://github.com/behave/behave). +other BDD tools, such as [Turnip](https://github.com/jnicklas/turnip), +[Behat](https://github.com/Behat/Behat) and +[Behave](https://github.com/behave/behave). Big thanks to Jonas Nicklas, Konstantin Kudryashov and Jens Engel for implementing those libraries. From dc180b242e24d73bd7b1414f6b1f99ba9af77838 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 8 Nov 2019 19:20:29 +0100 Subject: [PATCH 062/183] Add note about empty invalids --- cucumber-expressions/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 6551f55001..01a805ad9f 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -22,6 +22,8 @@ token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | '\{' | '{' | Note: * While `parameter` is allowed to appear as part of `alternative` and `option` in the AST, such an AST is not a valid a Cucumber Expression. + * ASTs with empty alternatives or alternatives that only + contain an optional are valid ASTs but invalid Cucumber Expressions. * All escaped tokens (tokens starting with a backslash) are rewritten to their unescaped equivalent after parsing. From 40e53858a365115cd6fdbe473fea35669ba232bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Tue, 14 Jan 2020 11:58:05 +0000 Subject: [PATCH 063/183] gofmt --- cucumber-expressions/go/cucumber_expression_parser_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index 342219f898..34c364b27a 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -118,7 +118,7 @@ func TestCucumberExpressionParser(t *testing.T) { expressionNode, []astNode{ {textNode, []astNode{}, token{"{", beginParameter}}, - {textNode, []astNode{}, token{"string",text}}, + {textNode, []astNode{}, token{"string", text}}, }, nullToken, }) From 7fb4d6ba1635ea2c95ff10f4629fe89a96150fa2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 6 Jun 2020 13:12:44 +0200 Subject: [PATCH 064/183] Reduce number of tokens used --- cucumber-expressions/java/examples.txt | 2 +- .../io/cucumber/cucumberexpressions/Ast.java | 55 +++--- .../CucumberExpressionParser.java | 62 +++--- .../CucumberExpressionTokenizer.java | 179 +++++++++--------- .../CucumberExpressionParserTest.java | 58 +++--- .../CucumberExpressionTokenizerTest.java | 68 ++++--- 6 files changed, 200 insertions(+), 224 deletions(-) diff --git a/cucumber-expressions/java/examples.txt b/cucumber-expressions/java/examples.txt index 35b7bd17a9..c6acb6958e 100644 --- a/cucumber-expressions/java/examples.txt +++ b/cucumber-expressions/java/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \[]^$.|?*+ +I have {int} cuke(s) and some \\[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 296783cc92..7650e3ee5a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -3,17 +3,10 @@ import java.util.List; import java.util.Objects; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATIVE_NODE; -import static io.cucumber.cucumberexpressions.Ast.Token.ALTERNATION; import static java.util.Arrays.asList; import static java.util.stream.Collectors.joining; final class Ast { - // Marker. This way we don't need to model the - // the tail end of alternation in the AST: - // - // alternation := alternative* + ( '/' + alternative* )+ - static final AstNode ALTERNATIVE_SEPARATOR = new AstNode(ALTERNATIVE_NODE, ALTERNATION); static final class AstNode { @@ -68,11 +61,34 @@ String getText() { @Override public String toString() { - return "AstNode{" + - "type=" + type + - ", nodes=" + nodes + - ", token=" + token + - '}'; + return toString(0).toString(); + } + + private StringBuilder toString(int depth){ + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < depth; i++) { + sb.append("\t"); + } + sb.append("AstNode{" + "type=").append(type); + + if (token != null) { + sb.append(", token=").append(token); + } + + if(nodes == null){ + sb.append('}'); + } else { + sb.append("\n"); + for (AstNode node : nodes) { + sb.append(node.toString(depth+1)); + sb.append("\n"); + } + for (int i = 0; i < depth; i++) { + sb.append("\t"); + } + sb.append('}'); + } + return sb; } @Override @@ -94,13 +110,6 @@ public int hashCode() { static final class Token { - static final Token BEGIN_PARAMETER = new Token("{", Token.Type.BEGIN_PARAMETER); - static final Token END_PARAMETER = new Token("}", Token.Type.END_PARAMETER); - static final Token BEGIN_OPTIONAL = new Token("(", Token.Type.BEGIN_OPTIONAL); - static final Token END_OPTIONAL = new Token(")", Token.Type.END_OPTIONAL); - static final Token ESCAPE = new Token("\\", Token.Type.ESCAPE); - static final Token ALTERNATION = new Token("/", Token.Type.ALTERNATION); - final String text; final Token.Type type; @@ -134,20 +143,12 @@ public String toString() { enum Type { START_OF_LINE, END_OF_LINE, - // In order of precedence - WHITE_SPACE_ESCAPED, WHITE_SPACE, - BEGIN_OPTIONAL_ESCAPED, BEGIN_OPTIONAL, - END_OPTIONAL_ESCAPED, END_OPTIONAL, - BEGIN_PARAMETER_ESCAPED, BEGIN_PARAMETER, - END_PARAMETER_ESCAPED, END_PARAMETER, - ALTERNATION_ESCAPED, ALTERNATION, - ESCAPE_ESCAPED, ESCAPE, TEXT; } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index ee27425eae..13e2f629af 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import java.util.List; -import static io.cucumber.cucumberexpressions.Ast.ALTERNATIVE_SEPARATOR; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATION_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATIVE_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.EXPRESSION_NODE; @@ -35,33 +34,10 @@ private interface Parser { * text := token */ private static final Parser textParser = (ast, expression, current) -> { - Token currentToken = expression.get(current); - Token unescaped = unEscape(currentToken); - ast.add(new AstNode(TEXT_NODE, unescaped)); + ast.add(new AstNode(TEXT_NODE, expression.get(current))); return 1; }; - private static Token unEscape(Token currentToken) { - switch (currentToken.type) { - case WHITE_SPACE_ESCAPED: - return new Token(currentToken.text.substring(1), Type.WHITE_SPACE); - case BEGIN_OPTIONAL_ESCAPED: - return Token.BEGIN_OPTIONAL; - case END_OPTIONAL_ESCAPED: - return Token.END_OPTIONAL; - case BEGIN_PARAMETER_ESCAPED: - return Token.BEGIN_PARAMETER; - case END_PARAMETER_ESCAPED: - return Token.END_PARAMETER; - case ALTERNATION_ESCAPED: - return Token.ALTERNATION; - case ESCAPE_ESCAPED: - return Token.ESCAPE; - default: - return currentToken; - } - } - /* * parameter := '{' + text* + '}' */ @@ -107,7 +83,13 @@ private static Parser parseBetween( }; } - private static Parser alternativeSeparator = (ast, expression, current) -> { + // Marker. This way we don't need to model the + // the tail end of alternation in the AST: + // + // alternation := alternative* + ( '/' + alternative* )+ + private static final AstNode ALTERNATIVE_SEPARATOR = new AstNode(ALTERNATIVE_NODE, new Token("/", Token.Type.ALTERNATION)); + + private static final Parser alternativeSeparator = (ast, expression, current) -> { if (!lookingAt(expression, current, ALTERNATION)) { return 0; } @@ -144,26 +126,32 @@ private static Parser parseBetween( return consumed; }; - private static final List cucumberExpressionParsers = asList( - alternationParser, - optionalParser, - parameterParser, - textParser - ); - /* * cucumber-expression := ( alternation | optional | parameter | text )* */ + private static final Parser cucumberExpressionParser = parseBetween( + EXPRESSION_NODE, + START_OF_LINE, + END_OF_LINE, + asList( + alternationParser, + optionalParser, + parameterParser, + textParser + ) + ); + + AstNode parse(String expression) { CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); List tokens = tokenizer.tokenize(expression); List ast = new ArrayList<>(); - int consumed = parseTokensUntil(cucumberExpressionParsers, ast, tokens, 0, END_OF_LINE); - if (consumed != tokens.size()) { + int consumed = cucumberExpressionParser.parse(ast, tokens, 0); + if (ast.size() != 1 || consumed < tokens.size()) { // If configured correctly this will never happen throw new IllegalStateException("Could not parse " + expression); } - return new AstNode(EXPRESSION_NODE, ast); + return ast.get(0); } private static int parseTokensUntil(List parsers, @@ -226,7 +214,7 @@ private static List splitAlternatives(List astNode) { List alternatives = new ArrayList<>(); List alternative = new ArrayList<>(); for (AstNode token : astNode) { - if (Ast.ALTERNATIVE_SEPARATOR.equals(token)) { + if (ALTERNATIVE_SEPARATOR.equals(token)) { alternatives.add(new AstNode(ALTERNATIVE_NODE, alternative)); alternative = new ArrayList<>(); } else { diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index bec27176f6..447b6a7512 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -1,120 +1,111 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.Ast.Token; +import io.cucumber.cucumberexpressions.Ast.Token.Type; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Iterator; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION_ESCAPED; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL_ESCAPED; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER_ESCAPED; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.ESCAPE; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.ESCAPE_ESCAPED; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE_ESCAPED; +import java.util.NoSuchElementException; +import java.util.PrimitiveIterator.OfInt; final class CucumberExpressionTokenizer { - private interface Tokenizer { - int tokenize(List tokens, String expression, int current); + List tokenize(String expression){ + List tokens = new ArrayList<>(); + tokenizeImpl(expression).forEach(tokens::add); + return tokens; } - private static final List tokenizers = Arrays.asList( - tokenizePattern(WHITE_SPACE_ESCAPED, Pattern.compile("\\\\\\s")), - tokenizePattern(WHITE_SPACE, Pattern.compile("\\s+")), - - tokenizeString(BEGIN_OPTIONAL_ESCAPED, "\\("), - tokenizeCharacter(BEGIN_OPTIONAL, '('), - - tokenizeString(END_OPTIONAL_ESCAPED, "\\)"), - tokenizeCharacter(END_OPTIONAL, ')'), - - tokenizeString(BEGIN_PARAMETER_ESCAPED, "\\{"), - tokenizeCharacter(BEGIN_PARAMETER, '{'), + private Iterable tokenizeImpl(String expression) { + return () -> new Iterator() { + final OfInt codePoints = expression.codePoints().iterator(); + StringBuilder buffer = new StringBuilder(); + Type previousTokenType = null; + Type currentTokenType = Type.START_OF_LINE; + boolean treatAsText = false; + + @Override + public boolean hasNext() { + return previousTokenType != Type.END_OF_LINE; + } - tokenizeString(END_PARAMETER_ESCAPED, "\\}"), - tokenizeCharacter(END_PARAMETER, '}'), + @Override + public Token next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } - tokenizeString(ALTERNATION_ESCAPED, "\\/"), - tokenizeCharacter(ALTERNATION, '/'), + if(currentTokenType == Type.START_OF_LINE){ + currentTokenType = null; + return new Token("", Type.START_OF_LINE); + } - tokenizeString(ESCAPE_ESCAPED, "\\\\"), - tokenizeString(ESCAPE, "\\"), + while (codePoints.hasNext()) { + int current = codePoints.nextInt(); + if (!treatAsText && current == '\\') { + treatAsText = true; + continue; + } + currentTokenType = tokenTypeOf(current, treatAsText); + treatAsText = false; + + if (previousTokenType != null + && (currentTokenType != previousTokenType + || (currentTokenType != Type.WHITE_SPACE && currentTokenType != Type.TEXT))) { + Token t = new Token(buffer.toString(), previousTokenType); + buffer = new StringBuilder(); + buffer.appendCodePoint(current); + previousTokenType = currentTokenType; + return t; + } + buffer.appendCodePoint(current); + previousTokenType = currentTokenType; + } - // Should be `.` but this creates a nicer parse tree. - tokenizePattern(TEXT, Pattern.compile("[^(){}\\\\/\\s]+")) - ); + if (buffer.length() > 0) { + Token t = new Token(buffer.toString(), previousTokenType); + buffer = new StringBuilder(); + currentTokenType = Type.END_OF_LINE; + return t; + } - /* - * token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | - * '\{' | '{' | '\}' | '}' | '\/' | '/' | '\\' | '\' | . - */ - List tokenize(String expression) { - List tokens = new ArrayList<>(); - int length = expression.length(); - int current = 0; - while (current < length) { - boolean tokenized = false; - for (Tokenizer tokenizer : tokenizers) { - int consumed = tokenizer.tokenize(tokens, expression, current); - if (consumed != 0) { - current += consumed; - tokenized = true; - break; + if (treatAsText) { + throw new CucumberExpressionException("End of line can not be escaped"); } - } - if (!tokenized) { - // Can't happen if configured properly - // Leave in to avoid looping if not configured properly - throw new IllegalStateException("Could not tokenize " + expression); - } - } - return tokens; - } - private static Tokenizer tokenizeCharacter(Token.Type type, char character) { - return (tokens, expression, current) -> { - if (character != expression.charAt(current)) { - return 0; + currentTokenType = null; + previousTokenType = Type.END_OF_LINE; + Token t = new Token(buffer.toString(), previousTokenType); + buffer = new StringBuilder(); + return t; } - tokens.add(new Token("" + character, type)); - return 1; }; - } - private static Tokenizer tokenizeString(Token.Type type, String string) { - return (tokens, expression, current) -> { - if (!expression.regionMatches(current, string, 0, string.length())) { - return 0; - } - tokens.add(new Token(string, type)); - return string.length(); - }; } - private static Tokenizer tokenizePattern(Token.Type type, Pattern pattern) { - return (tokens, expression, current) -> { - String tail = expression.substring(current); - Matcher matcher = pattern.matcher(tail); - if (!matcher.lookingAt()) { - return 0; - } - String match = tail.substring(0, matcher.end()); - tokens.add(new Token(match, type)); - return match.length(); - }; - } + private Type tokenTypeOf(Integer c, boolean treatAsText) { + if (treatAsText) { + return Type.TEXT; + } + + if (Character.isWhitespace(c)) { + return Type.WHITE_SPACE; + } + switch (c) { + case (int) '/': + return Type.ALTERNATION; + case (int) '{': + return Type.BEGIN_PARAMETER; + case (int) '}': + return Type.END_PARAMETER; + case (int) '(': + return Type.BEGIN_OPTIONAL; + case (int) ')': + return Type.END_OPTIONAL; + } + return Type.TEXT; + } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index dcaf89358a..c5eb66ae82 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -10,15 +10,15 @@ import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.OPTIONAL_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.ESCAPE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; class CucumberExpressionParserTest { @@ -91,10 +91,17 @@ void optionalPhrase() { } @Test - void slash() { - assertThat(astOf("\\"), equalTo( + void escapedEndOfLine() { + // TODO: Better error message + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\")); + assertThat(exception.getMessage(), is("End of line can not be escaped")); + } + + @Test + void escapedBackSlash() { + assertThat(astOf("\\\\"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("\\", ESCAPE)) + new AstNode(TEXT_NODE, new Token("\\", TEXT)) ) )); } @@ -131,7 +138,7 @@ void openingParenthesis() { void escapedOpeningParenthesis() { assertThat(astOf("\\("), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)) + new AstNode(TEXT_NODE, new Token("(", TEXT)) ) )); } @@ -140,8 +147,7 @@ void escapedOpeningParenthesis() { void escapedOptional() { assertThat(astOf("\\(blind)"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), - new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token("(blind", TEXT)), new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)) ) )); @@ -153,8 +159,7 @@ void escapedOptionalPhrase() { new AstNode(EXPRESSION_NODE, new AstNode(TEXT_NODE, new Token("three", TEXT)), new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), - new AstNode(TEXT_NODE, new Token("blind", TEXT)), + new AstNode(TEXT_NODE, new Token("(blind", TEXT)), new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), new AstNode(TEXT_NODE, new Token("mice", TEXT)) @@ -168,7 +173,7 @@ void escapedOptionalFollowedByOptional() { new AstNode(EXPRESSION_NODE, new AstNode(TEXT_NODE, new Token("three", TEXT)), new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), + new AstNode(TEXT_NODE, new Token("(", TEXT)), new AstNode(OPTIONAL_NODE, new AstNode(TEXT_NODE, new Token("very", TEXT)) ), @@ -189,8 +194,7 @@ void optionalContainingEscapedOptional() { new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), new AstNode(OPTIONAL_NODE, new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), - new AstNode(TEXT_NODE, new Token("very", TEXT)), - new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), + new AstNode(TEXT_NODE, new Token("very)", TEXT)), new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), new AstNode(TEXT_NODE, new Token("blind", TEXT)) ), @@ -200,7 +204,6 @@ void optionalContainingEscapedOptional() { )); } - @Test void alternation() { assertThat(astOf("mice/rats"), equalTo( @@ -220,14 +223,11 @@ void alternation() { void escapedAlternation() { assertThat(astOf("mice\\/rats"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("mice", TEXT)), - new AstNode(TEXT_NODE, new Token("/", ALTERNATION)), - new AstNode(TEXT_NODE, new Token("rats", TEXT)) + new AstNode(TEXT_NODE, new Token("mice/rats", TEXT)) ) )); } - @Test void alternationPhrase() { assertThat(astOf("three hungry/blind mice"), equalTo( @@ -254,16 +254,10 @@ void alternationWithWhiteSpace() { new AstNode(EXPRESSION_NODE, new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("hungry", TEXT)) + new AstNode(TEXT_NODE, new Token(" three hungry", TEXT)) ), new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("blind", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)) + new AstNode(TEXT_NODE, new Token("blind mice ", TEXT)) ) ) @@ -280,9 +274,7 @@ void alternationWithUnusedEndOptional() { new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), - new AstNode(TEXT_NODE, new Token("blind", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, new Token("blind mice", TEXT)) ), new AstNode(ALTERNATIVE_NODE, new AstNode(TEXT_NODE, new Token("rats", TEXT)) @@ -300,9 +292,7 @@ void alternationWithUnusedStartOptional() { new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("blind", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, new Token("blind mice", TEXT)) ), new AstNode(ALTERNATIVE_NODE, new AstNode(TEXT_NODE, new Token("rats", TEXT)), @@ -321,9 +311,7 @@ void alternationFollowedByOptional() { new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("blind", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("rat", TEXT)) + new AstNode(TEXT_NODE, new Token("blind rat", TEXT)) ), new AstNode(ALTERNATIVE_NODE, new AstNode(TEXT_NODE, new Token("cat", TEXT)), diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index be29a7869b..0c5dc86ab6 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -5,21 +5,16 @@ import org.junit.jupiter.api.Test; import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION_ESCAPED; import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL_ESCAPED; import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OF_LINE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL_ESCAPED; import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER_ESCAPED; +import static io.cucumber.cucumberexpressions.Ast.Token.Type.START_OF_LINE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE_ESCAPED; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; class CucumberExpressionTokenizerTest { @@ -28,79 +23,92 @@ class CucumberExpressionTokenizerTest { @Test void emptyString() { - assertThat(tokenizer.tokenize(""), empty()); + assertThat(tokenizer.tokenize(""), contains( + new Token("", START_OF_LINE), + new Token("", END_OF_LINE) + )); } @Test void phrase() { assertThat(tokenizer.tokenize("three blind mice"), contains( + new Token("", START_OF_LINE), new Token("three", TEXT), new Token(" ", WHITE_SPACE), new Token("blind", TEXT), new Token(" ", WHITE_SPACE), - new Token("mice", TEXT) + new Token("mice", TEXT), + new Token("", END_OF_LINE) )); } @Test void optional() { assertThat(tokenizer.tokenize("(blind)"), contains( + new Token("", START_OF_LINE), new Token("(", BEGIN_OPTIONAL), new Token("blind", TEXT), - new Token(")", END_OPTIONAL) + new Token(")", END_OPTIONAL), + new Token("", END_OF_LINE) )); } @Test void escapedOptional() { assertThat(tokenizer.tokenize("\\(blind\\)"), contains( - new Token("\\(", BEGIN_OPTIONAL_ESCAPED), - new Token("blind", TEXT), - new Token("\\)", END_OPTIONAL_ESCAPED) + new Token("", START_OF_LINE), + new Token("(blind)", TEXT), + new Token("", END_OF_LINE) )); } @Test void optionalPhrase() { assertThat(tokenizer.tokenize("three (blind) mice"), contains( + new Token("", START_OF_LINE), new Token("three", TEXT), new Token(" ", WHITE_SPACE), new Token("(", BEGIN_OPTIONAL), new Token("blind", TEXT), new Token(")", END_OPTIONAL), new Token(" ", WHITE_SPACE), - new Token("mice", TEXT) + new Token("mice", TEXT), + new Token("", END_OF_LINE) )); } @Test void parameter() { assertThat(tokenizer.tokenize("{string}"), contains( + new Token("", START_OF_LINE), new Token("{", BEGIN_PARAMETER), new Token("string", TEXT), - new Token("}", END_PARAMETER) + new Token("}", END_PARAMETER), + new Token("", END_OF_LINE) )); } @Test - void EscapedParameter() { + void escapedParameter() { assertThat(tokenizer.tokenize("\\{string\\}"), contains( - new Token("\\{", BEGIN_PARAMETER_ESCAPED), - new Token("string", TEXT), - new Token("\\}", END_PARAMETER_ESCAPED) + new Token("", START_OF_LINE), + new Token("{string}", TEXT), + new Token("", END_OF_LINE) )); } @Test void parameterPhrase() { assertThat(tokenizer.tokenize("three {string} mice"), contains( + new Token("", START_OF_LINE), new Token("three", TEXT), new Token(" ", WHITE_SPACE), new Token("{", BEGIN_PARAMETER), new Token("string", TEXT), new Token("}", END_PARAMETER), new Token(" ", WHITE_SPACE), - new Token("mice", TEXT) + new Token("mice", TEXT), + new Token("", END_OF_LINE) )); } @@ -108,37 +116,37 @@ void parameterPhrase() { @Test void alternation() { assertThat(tokenizer.tokenize("blind/cripple"), contains( + new Token("", START_OF_LINE), new Token("blind", TEXT), new Token("/", ALTERNATION), - new Token("cripple", TEXT) + new Token("cripple", TEXT), + new Token("", END_OF_LINE) )); } @Test void escapedAlternation() { assertThat(tokenizer.tokenize("blind\\ and\\ famished\\/cripple mice"), contains( - new Token("blind", TEXT), - new Token("\\ ", WHITE_SPACE_ESCAPED), - new Token("and", TEXT), - new Token("\\ ", WHITE_SPACE_ESCAPED), - new Token("famished", TEXT), - new Token("\\/", ALTERNATION_ESCAPED), - new Token("cripple", TEXT), + new Token("", START_OF_LINE), + new Token("blind and famished/cripple", TEXT), new Token(" ", WHITE_SPACE), - new Token("mice", TEXT) + new Token("mice", TEXT), + new Token("", END_OF_LINE) )); } @Test void alternationPhrase() { assertThat(tokenizer.tokenize("three blind/cripple mice"), contains( + new Token("", START_OF_LINE), new Token("three", TEXT), new Token(" ", WHITE_SPACE), new Token("blind", TEXT), new Token("/", ALTERNATION), new Token("cripple", TEXT), new Token(" ", WHITE_SPACE), - new Token("mice", TEXT) + new Token("mice", TEXT), + new Token("", END_OF_LINE) )); } } From 8685896eb965f092ab616d99ff9d52594c83ba12 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 6 Jun 2020 13:20:25 +0200 Subject: [PATCH 065/183] Reject incomplete expressions --- .../CucumberExpressionParser.java | 2 +- .../CucumberExpressionParserTest.java | 45 ++++++------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 13e2f629af..8f9386d9bb 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -75,7 +75,7 @@ private static Parser parseBetween( // endToken not found if (!lookingAt(expression, subCurrent, endToken)) { - return 0; + throw new CucumberExpressionException("missing " + endToken + " at " + subCurrent); } ast.add(new AstNode(type, subAst)); // consumes endToken diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index c5eb66ae82..1ad1e6988f 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -108,30 +108,23 @@ void escapedBackSlash() { @Test void openingBrace() { - assertThat(astOf("{"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("{", BEGIN_PARAMETER)) - ) - )); + //TODO: Improve message + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); + assertThat(exception.getMessage(), is("missing END_PARAMETER at 3")); } @Test void unfinishedParameter() { - assertThat(astOf("{string"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("{", BEGIN_PARAMETER)), - new AstNode(TEXT_NODE, new Token("string", TEXT)) - ) - )); + //TODO: Improve message + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); + assertThat(exception.getMessage(), is("missing END_PARAMETER at 4")); } @Test void openingParenthesis() { - assertThat(astOf("("), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)) - ) - )); + //TODO: Improve message + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); + assertThat(exception.getMessage(), is("missing END_OPTIONAL at 3")); } @Test @@ -286,21 +279,11 @@ void alternationWithUnusedEndOptional() { @Test void alternationWithUnusedStartOptional() { - assertThat(astOf("three blind\\ mice/rats("), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(ALTERNATION_NODE, - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("blind mice", TEXT)) - ), - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("rats", TEXT)), - new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)) - ) - ) - ) - )); + //TODO: Improve message + CucumberExpressionException exception = assertThrows( + CucumberExpressionException.class, + () -> astOf("three blind\\ mice/rats(")); + assertThat(exception.getMessage(), is("missing END_OPTIONAL at 8")); } @Test From f79c66294adf03a1d3be7efe2b68992bdd4b0d15 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 6 Jun 2020 13:32:07 +0200 Subject: [PATCH 066/183] Put parser definitions at the top of the file --- .../CucumberExpressionParser.java | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 8f9386d9bb..f2a57ea3c2 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -26,10 +26,6 @@ final class CucumberExpressionParser { - private interface Parser { - int parse(List ast, List expression, int current); - } - /* * text := token */ @@ -59,30 +55,6 @@ private interface Parser { asList(parameterParser, textParser) ); - private static Parser parseBetween( - AstNode.Type type, - Type beginToken, - Type endToken, - List parsers) { - return (ast, expression, current) -> { - if (!lookingAt(expression, current, beginToken)) { - return 0; - } - List subAst = new ArrayList<>(); - int subCurrent = current + 1; - int consumed = parseTokensUntil(parsers, subAst, expression, subCurrent, endToken); - subCurrent += consumed; - - // endToken not found - if (!lookingAt(expression, subCurrent, endToken)) { - throw new CucumberExpressionException("missing " + endToken + " at " + subCurrent); - } - ast.add(new AstNode(type, subAst)); - // consumes endToken - return subCurrent + 1 - current; - }; - } - // Marker. This way we don't need to model the // the tail end of alternation in the AST: // @@ -141,7 +113,6 @@ private static Parser parseBetween( ) ); - AstNode parse(String expression) { CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); List tokens = tokenizer.tokenize(expression); @@ -154,6 +125,35 @@ AstNode parse(String expression) { return ast.get(0); } + private interface Parser { + int parse(List ast, List expression, int current); + } + + private static Parser parseBetween( + AstNode.Type type, + Type beginToken, + Type endToken, + List parsers) { + return (ast, expression, current) -> { + if (!lookingAt(expression, current, beginToken)) { + return 0; + } + List subAst = new ArrayList<>(); + int subCurrent = current + 1; + int consumed = parseTokensUntil(parsers, subAst, expression, subCurrent, endToken); + subCurrent += consumed; + + // endToken not found + if (!lookingAt(expression, subCurrent, endToken)) { + throw new CucumberExpressionException("missing " + endToken + " at " + subCurrent); + } + ast.add(new AstNode(type, subAst)); + // consumes endToken + return subCurrent + 1 - current; + }; + } + + private static int parseTokensUntil(List parsers, List ast, List expression, @@ -170,7 +170,7 @@ private static int parseTokensUntil(List parsers, if (consumed == 0) { // If configured correctly this will never happen // Keep to avoid infinite loops - break; + throw new IllegalStateException("No eligible parsers for " + expression); } current += consumed; } From 638702f1d3cfb09593a7e581c9cd85acb9760f70 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 6 Jun 2020 13:42:36 +0200 Subject: [PATCH 067/183] Clean up --- .../io/cucumber/cucumberexpressions/Ast.java | 11 +++----- .../CucumberExpressionParserTest.java | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 7650e3ee5a..02d85819d1 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -32,7 +32,6 @@ private AstNode(Type type, List nodes, Token token) { this.token = token; } - enum Type { TEXT_NODE, OPTIONAL_NODE, @@ -75,19 +74,18 @@ private StringBuilder toString(int depth){ sb.append(", token=").append(token); } - if(nodes == null){ - sb.append('}'); - } else { + if (nodes != null) { sb.append("\n"); for (AstNode node : nodes) { - sb.append(node.toString(depth+1)); + sb.append(node.toString(depth + 1)); sb.append("\n"); } for (int i = 0; i < depth; i++) { sb.append("\t"); } - sb.append('}'); } + + sb.append('}'); return sb; } @@ -149,7 +147,6 @@ enum Type { BEGIN_PARAMETER, END_PARAMETER, ALTERNATION, - ESCAPE, TEXT; } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 1ad1e6988f..35f966bad4 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -11,7 +11,6 @@ import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; @@ -212,6 +211,31 @@ void alternation() { )); } + @Test + void emptyAlternation() { + assertThat(astOf("/"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(ALTERNATION_NODE, + new AstNode(ALTERNATIVE_NODE), + new AstNode(ALTERNATIVE_NODE) + ) + ) + )); + } + + @Test + void emptyAlternations() { + assertThat(astOf("//"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(ALTERNATION_NODE, + new AstNode(ALTERNATIVE_NODE), + new AstNode(ALTERNATIVE_NODE), + new AstNode(ALTERNATIVE_NODE) + ) + ) + )); + } + @Test void escapedAlternation() { assertThat(astOf("mice\\/rats"), equalTo( From df3f769cb9d1bac62740c2376ee01b65ad715820 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 6 Jun 2020 14:04:27 +0200 Subject: [PATCH 068/183] Remove tokens from AST --- cucumber-expressions/README.md | 3 +- .../io/cucumber/cucumberexpressions/Ast.java | 8 +- .../CucumberExpressionParser.java | 13 +- .../CucumberExpressionParserTest.java | 144 ++++++++++-------- 4 files changed, 89 insertions(+), 79 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 01a805ad9f..c85649ead1 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -15,8 +15,7 @@ option := parameter | text parameter := '{' + text* + '}' text := token -token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | '\{' | '{' | - '\}' | '}' | '\/' | '/' | '\\' | '\' | . +token := '\' | whitespace | '(' | ')' | '{' | '}' | '/' | . ``` Note: diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 02d85819d1..fe521b06c5 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -12,9 +12,9 @@ static final class AstNode { private final Type type; private final List nodes; - private final Token token; + private final String token; - AstNode(Type type, Token token) { + AstNode(Type type, String token) { this(type, null, token); } @@ -26,7 +26,7 @@ static final class AstNode { this(type, nodes, null); } - private AstNode(Type type, List nodes, Token token) { + private AstNode(Type type, List nodes, String token) { this.type = type; this.nodes = nodes; this.token = token; @@ -51,7 +51,7 @@ Type getType() { String getText() { if(token != null) - return token.text; + return token; return getNodes().stream() .map(AstNode::getText) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index f2a57ea3c2..7f27d7bcc7 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -30,7 +30,8 @@ final class CucumberExpressionParser { * text := token */ private static final Parser textParser = (ast, expression, current) -> { - ast.add(new AstNode(TEXT_NODE, expression.get(current))); + Token token = expression.get(current); + ast.add(new AstNode(TEXT_NODE, token.text)); return 1; }; @@ -59,7 +60,7 @@ final class CucumberExpressionParser { // the tail end of alternation in the AST: // // alternation := alternative* + ( '/' + alternative* )+ - private static final AstNode ALTERNATIVE_SEPARATOR = new AstNode(ALTERNATIVE_NODE, new Token("/", Token.Type.ALTERNATION)); + private static final AstNode ALTERNATIVE_SEPARATOR = new AstNode(ALTERNATIVE_NODE, "/"); private static final Parser alternativeSeparator = (ast, expression, current) -> { if (!lookingAt(expression, current, ALTERNATION)) { @@ -117,11 +118,7 @@ AstNode parse(String expression) { CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); List tokens = tokenizer.tokenize(expression); List ast = new ArrayList<>(); - int consumed = cucumberExpressionParser.parse(ast, tokens, 0); - if (ast.size() != 1 || consumed < tokens.size()) { - // If configured correctly this will never happen - throw new IllegalStateException("Could not parse " + expression); - } + cucumberExpressionParser.parse(ast, tokens, 0); return ast.get(0); } @@ -188,7 +185,7 @@ private static int parseToken(List parsers, } } // If configured correctly this will never happen - return 0; + throw new IllegalStateException("No eligible parsers for " + expression); } private static boolean lookingAt(List expression, int at, Type... tokens) { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 35f966bad4..d60600ade4 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -1,7 +1,6 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.Ast.AstNode; -import io.cucumber.cucumberexpressions.Ast.Token; import org.junit.jupiter.api.Test; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATION_NODE; @@ -10,10 +9,6 @@ import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.OPTIONAL_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -34,11 +29,11 @@ void emptyString() { void phrase() { assertThat(astOf("three blind mice"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("blind", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, "three"), + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "blind"), + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "mice") ) )); } @@ -48,7 +43,7 @@ void optional() { assertThat(astOf("(blind)"), equalTo( new AstNode(EXPRESSION_NODE, new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, new Token("blind", TEXT)) + new AstNode(TEXT_NODE, "blind") ) ) )); @@ -59,7 +54,7 @@ void parameter() { assertThat(astOf("{string}"), equalTo( new AstNode(EXPRESSION_NODE, new AstNode(PARAMETER_NODE, - new AstNode(TEXT_NODE, new Token("string", TEXT)) + new AstNode(TEXT_NODE, "string") ) ) )); @@ -78,13 +73,13 @@ void anonymousParameter() { void optionalPhrase() { assertThat(astOf("three (blind) mice"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, "three"), + new AstNode(TEXT_NODE, " "), new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, new Token("blind", TEXT)) + new AstNode(TEXT_NODE, "blind") ), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "mice") ) )); } @@ -100,7 +95,7 @@ void escapedEndOfLine() { void escapedBackSlash() { assertThat(astOf("\\\\"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("\\", TEXT)) + new AstNode(TEXT_NODE, "\\") ) )); } @@ -112,6 +107,15 @@ void openingBrace() { assertThat(exception.getMessage(), is("missing END_PARAMETER at 3")); } + @Test + void closingBrace() { + assertThat(astOf("}"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, "}") + ) + )); + } + @Test void unfinishedParameter() { //TODO: Improve message @@ -126,11 +130,20 @@ void openingParenthesis() { assertThat(exception.getMessage(), is("missing END_OPTIONAL at 3")); } + @Test + void closingParenthesis() { + assertThat(astOf(")"), equalTo( + new AstNode(EXPRESSION_NODE, + new AstNode(TEXT_NODE, ")") + ) + )); + } + @Test void escapedOpeningParenthesis() { assertThat(astOf("\\("), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("(", TEXT)) + new AstNode(TEXT_NODE, "(") ) )); } @@ -139,8 +152,8 @@ void escapedOpeningParenthesis() { void escapedOptional() { assertThat(astOf("\\(blind)"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("(blind", TEXT)), - new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)) + new AstNode(TEXT_NODE, "(blind"), + new AstNode(TEXT_NODE, ")") ) )); } @@ -149,12 +162,12 @@ void escapedOptional() { void escapedOptionalPhrase() { assertThat(astOf("three \\(blind) mice"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("(blind", TEXT)), - new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, "three"), + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "(blind"), + new AstNode(TEXT_NODE, ")"), + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "mice") ) )); } @@ -163,17 +176,17 @@ void escapedOptionalPhrase() { void escapedOptionalFollowedByOptional() { assertThat(astOf("three \\((very) blind) mice"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("(", TEXT)), + new AstNode(TEXT_NODE, "three"), + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "("), new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, new Token("very", TEXT)) + new AstNode(TEXT_NODE, "very") ), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("blind", TEXT)), - new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "blind"), + new AstNode(TEXT_NODE, ")"), + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "mice") ) )); } @@ -182,16 +195,16 @@ void escapedOptionalFollowedByOptional() { void optionalContainingEscapedOptional() { assertThat(astOf("three ((very\\) blind) mice"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, "three"), + new AstNode(TEXT_NODE, " "), new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, new Token("(", BEGIN_OPTIONAL)), - new AstNode(TEXT_NODE, new Token("very)", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("blind", TEXT)) + new AstNode(TEXT_NODE, "("), + new AstNode(TEXT_NODE, "very)"), + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "blind") ), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "mice") ) )); } @@ -202,10 +215,11 @@ void alternation() { new AstNode(EXPRESSION_NODE, new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, "mice") ), new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("rats", TEXT))) + new AstNode(TEXT_NODE, "rats") + ) ) ) )); @@ -240,7 +254,7 @@ void emptyAlternations() { void escapedAlternation() { assertThat(astOf("mice\\/rats"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("mice/rats", TEXT)) + new AstNode(TEXT_NODE, "mice/rats") ) )); } @@ -249,18 +263,18 @@ void escapedAlternation() { void alternationPhrase() { assertThat(astOf("three hungry/blind mice"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, "three"), + new AstNode(TEXT_NODE, " "), new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("hungry", TEXT)) + new AstNode(TEXT_NODE, "hungry") ), new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("blind", TEXT)) + new AstNode(TEXT_NODE, "blind") ) ), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), - new AstNode(TEXT_NODE, new Token("mice", TEXT)) + new AstNode(TEXT_NODE, " "), + new AstNode(TEXT_NODE, "mice") ) )); } @@ -271,10 +285,10 @@ void alternationWithWhiteSpace() { new AstNode(EXPRESSION_NODE, new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token(" three hungry", TEXT)) + new AstNode(TEXT_NODE, " three hungry") ), new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("blind mice ", TEXT)) + new AstNode(TEXT_NODE, "blind mice ") ) ) @@ -286,15 +300,15 @@ void alternationWithWhiteSpace() { void alternationWithUnusedEndOptional() { assertThat(astOf("three )blind\\ mice/rats"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, "three"), + new AstNode(TEXT_NODE, " "), new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token(")", END_OPTIONAL)), - new AstNode(TEXT_NODE, new Token("blind mice", TEXT)) + new AstNode(TEXT_NODE, ")"), + new AstNode(TEXT_NODE, "blind mice") ), new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("rats", TEXT)) + new AstNode(TEXT_NODE, "rats") ) ) ) @@ -314,16 +328,16 @@ void alternationWithUnusedStartOptional() { void alternationFollowedByOptional() { assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, new Token("three", TEXT)), - new AstNode(TEXT_NODE, new Token(" ", WHITE_SPACE)), + new AstNode(TEXT_NODE, "three"), + new AstNode(TEXT_NODE, " "), new AstNode(ALTERNATION_NODE, new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("blind rat", TEXT)) + new AstNode(TEXT_NODE, "blind rat") ), new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, new Token("cat", TEXT)), + new AstNode(TEXT_NODE, "cat"), new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, new Token("s", TEXT)) + new AstNode(TEXT_NODE, "s") ) ) ) From 93d3f68a15ed3391006a8a5f47afd2e1aa1eefb0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 6 Jun 2020 14:22:04 +0200 Subject: [PATCH 069/183] Remove out variable --- .../CucumberExpressionParser.java | 81 ++++++++++--------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 7f27d7bcc7..266788c88f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -5,6 +5,7 @@ import io.cucumber.cucumberexpressions.Ast.Token.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATION_NODE; @@ -29,10 +30,9 @@ final class CucumberExpressionParser { /* * text := token */ - private static final Parser textParser = (ast, expression, current) -> { + private static final Parser textParser = (expression, current) -> { Token token = expression.get(current); - ast.add(new AstNode(TEXT_NODE, token.text)); - return 1; + return new Result(1, new AstNode(TEXT_NODE, token.text)); }; /* @@ -62,12 +62,11 @@ final class CucumberExpressionParser { // alternation := alternative* + ( '/' + alternative* )+ private static final AstNode ALTERNATIVE_SEPARATOR = new AstNode(ALTERNATIVE_NODE, "/"); - private static final Parser alternativeSeparator = (ast, expression, current) -> { + private static final Parser alternativeSeparator = (expression, current) -> { if (!lookingAt(expression, current, ALTERNATION)) { - return 0; + return new Result(0); } - ast.add(ALTERNATIVE_SEPARATOR); - return 1; + return new Result(1, ALTERNATIVE_SEPARATOR); }; private static final List alternativeParsers = asList( @@ -82,21 +81,19 @@ final class CucumberExpressionParser { * boundary := whitespace | ^ | $ * alternative: = optional | parameter | text */ - private static final Parser alternationParser = (ast, expression, current) -> { + private static final Parser alternationParser = (expression, current) -> { int previous = current - 1; if (!lookingAt(expression, previous, START_OF_LINE, WHITE_SPACE)) { - return 0; + return new Result(0); } - List subAst = new ArrayList<>(); - int consumed = parseTokensUntil(alternativeParsers, subAst, expression, current, WHITE_SPACE, END_OF_LINE); - if (!subAst.contains(ALTERNATIVE_SEPARATOR)) { - return 0; + Result result = parseTokensUntil(alternativeParsers, expression, current, WHITE_SPACE, END_OF_LINE); + if (!result.ast.contains(ALTERNATIVE_SEPARATOR)) { + return new Result(0); } - ast.add(new AstNode(ALTERNATION_NODE, splitAlternatives(subAst))); // Does not consume right hand boundary token - return consumed; + return new Result(result.consumed, new AstNode(ALTERNATION_NODE, splitAlternatives(result.ast))); }; /* @@ -117,13 +114,25 @@ final class CucumberExpressionParser { AstNode parse(String expression) { CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); List tokens = tokenizer.tokenize(expression); - List ast = new ArrayList<>(); - cucumberExpressionParser.parse(ast, tokens, 0); - return ast.get(0); + Result result = cucumberExpressionParser.parse(tokens, 0); + return result.ast.get(0); } private interface Parser { - int parse(List ast, List expression, int current); + Result parse(List expression, int current); + } + + private static final class Result { + final int consumed; + final List ast; + + private Result(int consumed, AstNode... ast) { + this(consumed, Arrays.asList(ast)); + } + private Result(int consumed, List ast) { + this.consumed = consumed; + this.ast = ast; + } } private static Parser parseBetween( @@ -131,57 +140,55 @@ private static Parser parseBetween( Type beginToken, Type endToken, List parsers) { - return (ast, expression, current) -> { + return (expression, current) -> { if (!lookingAt(expression, current, beginToken)) { - return 0; + return new Result(0); } - List subAst = new ArrayList<>(); int subCurrent = current + 1; - int consumed = parseTokensUntil(parsers, subAst, expression, subCurrent, endToken); - subCurrent += consumed; + Result result = parseTokensUntil(parsers, expression, subCurrent, endToken); + subCurrent += result.consumed; // endToken not found if (!lookingAt(expression, subCurrent, endToken)) { throw new CucumberExpressionException("missing " + endToken + " at " + subCurrent); } - ast.add(new AstNode(type, subAst)); // consumes endToken - return subCurrent + 1 - current; + return new Result(subCurrent + 1 - current, new AstNode(type, result.ast)); }; } - private static int parseTokensUntil(List parsers, - List ast, + private static Result parseTokensUntil(List parsers, List expression, int startAt, Type... endTokens) { int current = startAt; int size = expression.size(); + List ast = new ArrayList<>(); while (current < size) { if (lookingAt(expression, current, endTokens)) { break; } - int consumed = parseToken(parsers, ast, expression, current); - if (consumed == 0) { + Result result = parseToken(parsers, expression, current); + if (result.consumed == 0) { // If configured correctly this will never happen // Keep to avoid infinite loops throw new IllegalStateException("No eligible parsers for " + expression); } - current += consumed; + current += result.consumed; + ast.addAll(result.ast); } - return current - startAt; + return new Result(current - startAt, ast); } - private static int parseToken(List parsers, - List ast, + private static Result parseToken(List parsers, List expression, int startAt) { for (Parser parser : parsers) { - int consumed = parser.parse(ast, expression, startAt); - if (consumed != 0) { - return consumed; + Result result = parser.parse(expression, startAt); + if (result.consumed != 0) { + return result; } } // If configured correctly this will never happen From eb3bdc7156f893241bf183413be2b115242d7b04 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 10 Jul 2020 12:58:18 +0200 Subject: [PATCH 070/183] Improve exceptions --- .../io/cucumber/cucumberexpressions/Ast.java | 32 +++++++++++--- .../CucumberExpressionParseException.java | 34 ++++++++++++++ .../CucumberExpressionParser.java | 4 +- .../CucumberExpressionParserTest.java | 44 +++++++++++++------ 4 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index fe521b06c5..3797d50c26 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -142,12 +142,34 @@ enum Type { START_OF_LINE, END_OF_LINE, WHITE_SPACE, - BEGIN_OPTIONAL, - END_OPTIONAL, - BEGIN_PARAMETER, - END_PARAMETER, - ALTERNATION, + BEGIN_OPTIONAL('(', "optional text"), + END_OPTIONAL(')', "optional text"), + BEGIN_PARAMETER('{', "a parameter"), + END_PARAMETER('}', "a parameter"), + ALTERNATION('/', "alternation"), TEXT; + + private final int token; + private final String purpose; + + Type(){ this(-1, null);} + + Type(int token, String purpose) { + this.token = token; + this.purpose = purpose; + } + + public String getPurpose() { + return purpose; + } + + int codePoint() { + if (token == -1) { + throw new IllegalStateException(name() + " does not have a code point"); + } + + return token; + } } } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java new file mode 100644 index 0000000000..564d3bea4a --- /dev/null +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java @@ -0,0 +1,34 @@ +package io.cucumber.cucumberexpressions; + +import io.cucumber.cucumberexpressions.Ast.Token; +import io.cucumber.cucumberexpressions.Ast.Token.Type; + +import java.util.List; +import java.util.stream.Collectors; + +class CucumberExpressionParseException extends CucumberExpressionException { + + CucumberExpressionParseException(String message) { + super(message); + } + + static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List expression, int current) { + String expr = expression.stream().map(token -> token.text).collect(Collectors.joining()); + int currentInExpr = expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); + StringBuilder pointer = new StringBuilder(); + for (int i = 0; i < currentInExpr; i++) { + pointer.append(" "); + } + pointer.append("^"); + + return new CucumberExpressionParseException( + new StringBuilder() + .append("This Cucumber Expression has problem:").append("\n") + .append("\n") + .append(expr).append("\n") + .append(pointer).append("\n") + .append("The '").appendCodePoint(beginToken.codePoint()).append("' at ").append(pointer.length()).append(" did not have a matching '").appendCodePoint(endToken.codePoint()).append("'. ").append("\n") + .append("If you did not intended to use ").append(beginToken.getPurpose()).append(" you can use '\\").appendCodePoint(beginToken.codePoint()).append("' to escape the ").append(beginToken.getPurpose()).append("\n") + .toString()); + } +} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 266788c88f..9df3fdd35a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -22,6 +22,7 @@ import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; import static io.cucumber.cucumberexpressions.Ast.Token.Type.START_OF_LINE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; +import static io.cucumber.cucumberexpressions.CucumberExpressionParseException.createMissingEndTokenException; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -150,14 +151,13 @@ private static Parser parseBetween( // endToken not found if (!lookingAt(expression, subCurrent, endToken)) { - throw new CucumberExpressionException("missing " + endToken + " at " + subCurrent); + throw createMissingEndTokenException(beginToken, endToken, expression, current); } // consumes endToken return new Result(subCurrent + 1 - current, new AstNode(type, result.ast)); }; } - private static Result parseTokensUntil(List parsers, List expression, int startAt, diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index d60600ade4..e4081d63ee 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -102,9 +102,14 @@ void escapedBackSlash() { @Test void openingBrace() { - //TODO: Improve message - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); - assertThat(exception.getMessage(), is("missing END_PARAMETER at 3")); + CucumberExpressionParseException exception = assertThrows(CucumberExpressionParseException.class, () -> astOf("{")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + "\n" + + "{\n" + + "^\n" + + "The '{' at 1 did not have a matching '}'. \n" + + "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n" + )); } @Test @@ -118,16 +123,25 @@ void closingBrace() { @Test void unfinishedParameter() { - //TODO: Improve message - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); - assertThat(exception.getMessage(), is("missing END_PARAMETER at 4")); + CucumberExpressionParseException exception = assertThrows(CucumberExpressionParseException.class, () -> astOf("{string")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + "\n" + + "{string\n" + + "^\n" + + "The '{' at 1 did not have a matching '}'. \n" + + "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n")); } @Test void openingParenthesis() { - //TODO: Improve message - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); - assertThat(exception.getMessage(), is("missing END_OPTIONAL at 3")); + CucumberExpressionParseException exception = assertThrows(CucumberExpressionParseException.class, () -> astOf("(")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + "\n" + + "(\n" + + "^\n" + + "The '(' at 1 did not have a matching ')'. \n" + + "If you did not intended to use optional text you can use '\\(' to escape the optional text\n" + )); } @Test @@ -317,11 +331,15 @@ void alternationWithUnusedEndOptional() { @Test void alternationWithUnusedStartOptional() { - //TODO: Improve message - CucumberExpressionException exception = assertThrows( - CucumberExpressionException.class, + CucumberExpressionParseException exception = assertThrows( + CucumberExpressionParseException.class, () -> astOf("three blind\\ mice/rats(")); - assertThat(exception.getMessage(), is("missing END_OPTIONAL at 8")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + "\n" + + "three blind mice/rats(\n" + + " ^\n" + + "The '(' at 22 did not have a matching ')'. \n" + + "If you did not intended to use optional text you can use '\\(' to escape the optional text\n")); } @Test From bd82829f4911ce3a27130fdc9ed993485a8f3df3 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 10 Jul 2020 13:06:31 +0200 Subject: [PATCH 071/183] Clean up --- .../io/cucumber/cucumberexpressions/Ast.java | 40 +++++++++---------- .../CucumberExpressionParseException.java | 31 ++++++++------ 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 3797d50c26..c1c286cf4b 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -4,6 +4,7 @@ import java.util.Objects; import static java.util.Arrays.asList; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; final class Ast { @@ -50,7 +51,7 @@ Type getType() { } String getText() { - if(token != null) + if (token != null) return token; return getNodes().stream() @@ -63,7 +64,7 @@ public String toString() { return toString(0).toString(); } - private StringBuilder toString(int depth){ + private StringBuilder toString(int depth) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < depth; i++) { sb.append("\t"); @@ -105,7 +106,6 @@ public int hashCode() { } } - static final class Token { final String text; @@ -142,34 +142,34 @@ enum Type { START_OF_LINE, END_OF_LINE, WHITE_SPACE, - BEGIN_OPTIONAL('(', "optional text"), - END_OPTIONAL(')', "optional text"), - BEGIN_PARAMETER('{', "a parameter"), - END_PARAMETER('}', "a parameter"), - ALTERNATION('/', "alternation"), + BEGIN_OPTIONAL("(", "optional text"), + END_OPTIONAL(")", "optional text"), + BEGIN_PARAMETER("{", "a parameter"), + END_PARAMETER("}", "a parameter"), + ALTERNATION("/", "alternation"), TEXT; - private final int token; + private final String symbol; private final String purpose; - Type(){ this(-1, null);} + Type() { + this(null, null); + } - Type(int token, String purpose) { - this.token = token; + Type(String symbol, String purpose) { + this.symbol = symbol; this.purpose = purpose; } - public String getPurpose() { - return purpose; + String getPurpose() { + return requireNonNull(purpose, name() + " does not have a purpose"); } - int codePoint() { - if (token == -1) { - throw new IllegalStateException(name() + " does not have a code point"); - } - - return token; + String symbol() { + return requireNonNull(symbol, name() + " does not have a symbol"); } } + } + } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java index 564d3bea4a..4d72602a5c 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java @@ -12,23 +12,30 @@ class CucumberExpressionParseException extends CucumberExpressionException { super(message); } - static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List expression, int current) { - String expr = expression.stream().map(token -> token.text).collect(Collectors.joining()); + static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List tokens, int current) { + String expression = createExpressionString(tokens); + StringBuilder pointer = createPointer(tokens, current); + return new CucumberExpressionParseException( + "This Cucumber Expression has problem:" + "\n" + + "\n" + + expression + "\n" + + pointer + "\n" + + "The '" + beginToken.symbol() + "' at " + pointer.length() + " did not have a matching '" + endToken.symbol() + "'. " + "\n" + + "If you did not intended to use " + beginToken.getPurpose() + " you can use '\\" + beginToken.symbol() + "' to escape the " + beginToken.getPurpose() + "\n"); + } + + private static String createExpressionString(List expression) { + return expression.stream().map(token -> token.text).collect(Collectors.joining()); + } + + private static StringBuilder createPointer(List expression, int current) { int currentInExpr = expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); StringBuilder pointer = new StringBuilder(); for (int i = 0; i < currentInExpr; i++) { pointer.append(" "); } pointer.append("^"); - - return new CucumberExpressionParseException( - new StringBuilder() - .append("This Cucumber Expression has problem:").append("\n") - .append("\n") - .append(expr).append("\n") - .append(pointer).append("\n") - .append("The '").appendCodePoint(beginToken.codePoint()).append("' at ").append(pointer.length()).append(" did not have a matching '").appendCodePoint(endToken.codePoint()).append("'. ").append("\n") - .append("If you did not intended to use ").append(beginToken.getPurpose()).append(" you can use '\\").appendCodePoint(beginToken.codePoint()).append("' to escape the ").append(beginToken.getPurpose()).append("\n") - .toString()); + return pointer; } + } From 8bcfad020ada484bde939bfa3186ab435958cb87 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 10 Jul 2020 13:10:08 +0200 Subject: [PATCH 072/183] Clean up --- .../java/io/cucumber/cucumberexpressions/Ast.java | 2 +- .../CucumberExpressionParseException.java | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index c1c286cf4b..79417d36f2 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -161,7 +161,7 @@ enum Type { this.purpose = purpose; } - String getPurpose() { + String purpose() { return requireNonNull(purpose, name() + " does not have a purpose"); } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java index 4d72602a5c..73a38754df 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java @@ -13,22 +13,20 @@ class CucumberExpressionParseException extends CucumberExpressionException { } static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List tokens, int current) { - String expression = createExpressionString(tokens); - StringBuilder pointer = createPointer(tokens, current); return new CucumberExpressionParseException( "This Cucumber Expression has problem:" + "\n" + "\n" + - expression + "\n" + - pointer + "\n" + - "The '" + beginToken.symbol() + "' at " + pointer.length() + " did not have a matching '" + endToken.symbol() + "'. " + "\n" + - "If you did not intended to use " + beginToken.getPurpose() + " you can use '\\" + beginToken.symbol() + "' to escape the " + beginToken.getPurpose() + "\n"); + expressionOf(tokens) + "\n" + + pointAtCurrentToken(tokens, current) + "\n" + + "The '" + beginToken.symbol() + "' at " + pointAtCurrentToken(tokens, current).length() + " did not have a matching '" + endToken.symbol() + "'. " + "\n" + + "If you did not intended to use " + beginToken.purpose() + " you can use '\\" + beginToken.symbol() + "' to escape the " + beginToken.purpose() + "\n"); } - private static String createExpressionString(List expression) { + private static String expressionOf(List expression) { return expression.stream().map(token -> token.text).collect(Collectors.joining()); } - private static StringBuilder createPointer(List expression, int current) { + private static StringBuilder pointAtCurrentToken(List expression, int current) { int currentInExpr = expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); StringBuilder pointer = new StringBuilder(); for (int i = 0; i < currentInExpr; i++) { From a0872e743cdfed2641c66bfa3289a234ffbe54dd Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 10 Jul 2020 13:20:51 +0200 Subject: [PATCH 073/183] Clean up --- .../cucumberexpressions/Argument.java | 28 +-------- .../CucumberExpressionException.java | 62 +++++++++++++++++++ .../CucumberExpressionParseException.java | 39 ------------ .../CucumberExpressionParser.java | 2 +- .../CucumberExpressionParserTest.java | 10 +-- 5 files changed, 71 insertions(+), 70 deletions(-) delete mode 100644 cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Argument.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Argument.java index b5d6549f44..7daf274185 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Argument.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Argument.java @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.List; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createCaptureGroupParameterTypeMisMatch; + @API(status = API.Status.STABLE) public final class Argument { private final ParameterType parameterType; @@ -15,13 +17,7 @@ static List> build(Group group, TreeRegexp treeRegexp, List argGroups = group.getChildren(); if (argGroups.size() != parameterTypes.size()) { - throw new CucumberExpressionException(String.format("Expression /%s/ has %s capture groups (%s), but there were %s parameter types (%s)", - treeRegexp.pattern().pattern(), - argGroups.size(), - getGroupValues(argGroups), - parameterTypes.size(), - getParameterTypeNames(parameterTypes) - )); + throw createCaptureGroupParameterTypeMisMatch(treeRegexp, parameterTypes, argGroups); } List> args = new ArrayList<>(argGroups.size()); for (int i = 0; i < parameterTypes.size(); i++) { @@ -33,24 +29,6 @@ static List> build(Group group, TreeRegexp treeRegexp, List getParameterTypeNames(List> parameterTypes) { - List list = new ArrayList<>(); - for (ParameterType type : parameterTypes) { - String name = type.getName(); - list.add(name); - } - return list; - } - - private static List getGroupValues(List argGroups) { - List list = new ArrayList<>(); - for (Group argGroup : argGroups) { - String value = argGroup.getValue(); - list.add(value); - } - return list; - } - private Argument(Group group, ParameterType parameterType) { this.group = group; this.parameterType = parameterType; diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 443abc0e03..5c72eff997 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -1,7 +1,13 @@ package io.cucumber.cucumberexpressions; +import io.cucumber.cucumberexpressions.Ast.Token; +import io.cucumber.cucumberexpressions.Ast.Token.Type; import org.apiguardian.api.API; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + @API(status = API.Status.STABLE) public class CucumberExpressionException extends RuntimeException { CucumberExpressionException(String message) { @@ -11,4 +17,60 @@ public class CucumberExpressionException extends RuntimeException { CucumberExpressionException(String message, Throwable cause) { super(message, cause); } + + + + static CucumberExpressionException createCaptureGroupParameterTypeMisMatch(TreeRegexp treeRegexp, + List> parameterTypes, List argGroups) { + return new CucumberExpressionException(String.format("Expression /%s/ has %s capture groups (%s), but there were %s parameter types (%s)", + treeRegexp.pattern().pattern(), + argGroups.size(), + getGroupValues(argGroups), + parameterTypes.size(), + getParameterTypeNames(parameterTypes) + )); + } + + private static List getParameterTypeNames(List> parameterTypes) { + List list = new ArrayList<>(); + for (ParameterType type : parameterTypes) { + String name = type.getName(); + list.add(name); + } + return list; + } + + private static List getGroupValues(List argGroups) { + List list = new ArrayList<>(); + for (Group argGroup : argGroups) { + String value = argGroup.getValue(); + list.add(value); + } + return list; + } + + static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List tokens, int current) { + return new CucumberExpressionException( + "This Cucumber Expression has problem:" + "\n" + + "\n" + + expressionOf(tokens) + "\n" + + pointAtCurrentToken(tokens, current) + "\n" + + "The '" + beginToken.symbol() + "' at " + pointAtCurrentToken(tokens, current).length() + " did not have a matching '" + endToken.symbol() + "'. " + "\n" + + "If you did not intended to use " + beginToken.purpose() + " you can use '\\" + beginToken.symbol() + "' to escape the " + beginToken.purpose() + "\n"); + } + + private static String expressionOf(List expression) { + return expression.stream().map(token -> token.text).collect(Collectors.joining()); + } + + private static StringBuilder pointAtCurrentToken(List expression, int current) { + int currentInExpr = expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); + StringBuilder pointer = new StringBuilder(); + for (int i = 0; i < currentInExpr; i++) { + pointer.append(" "); + } + pointer.append("^"); + return pointer; + } + } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java deleted file mode 100644 index 73a38754df..0000000000 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParseException.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.cucumber.cucumberexpressions; - -import io.cucumber.cucumberexpressions.Ast.Token; -import io.cucumber.cucumberexpressions.Ast.Token.Type; - -import java.util.List; -import java.util.stream.Collectors; - -class CucumberExpressionParseException extends CucumberExpressionException { - - CucumberExpressionParseException(String message) { - super(message); - } - - static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List tokens, int current) { - return new CucumberExpressionParseException( - "This Cucumber Expression has problem:" + "\n" + - "\n" + - expressionOf(tokens) + "\n" + - pointAtCurrentToken(tokens, current) + "\n" + - "The '" + beginToken.symbol() + "' at " + pointAtCurrentToken(tokens, current).length() + " did not have a matching '" + endToken.symbol() + "'. " + "\n" + - "If you did not intended to use " + beginToken.purpose() + " you can use '\\" + beginToken.symbol() + "' to escape the " + beginToken.purpose() + "\n"); - } - - private static String expressionOf(List expression) { - return expression.stream().map(token -> token.text).collect(Collectors.joining()); - } - - private static StringBuilder pointAtCurrentToken(List expression, int current) { - int currentInExpr = expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); - StringBuilder pointer = new StringBuilder(); - for (int i = 0; i < currentInExpr; i++) { - pointer.append(" "); - } - pointer.append("^"); - return pointer; - } - -} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 9df3fdd35a..da1fc9d780 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -22,7 +22,7 @@ import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; import static io.cucumber.cucumberexpressions.Ast.Token.Type.START_OF_LINE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; -import static io.cucumber.cucumberexpressions.CucumberExpressionParseException.createMissingEndTokenException; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createMissingEndTokenException; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index e4081d63ee..159a39d8a0 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -102,7 +102,7 @@ void escapedBackSlash() { @Test void openingBrace() { - CucumberExpressionParseException exception = assertThrows(CucumberExpressionParseException.class, () -> astOf("{")); + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + "\n" + "{\n" + @@ -123,7 +123,7 @@ void closingBrace() { @Test void unfinishedParameter() { - CucumberExpressionParseException exception = assertThrows(CucumberExpressionParseException.class, () -> astOf("{string")); + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + "\n" + "{string\n" + @@ -134,7 +134,7 @@ void unfinishedParameter() { @Test void openingParenthesis() { - CucumberExpressionParseException exception = assertThrows(CucumberExpressionParseException.class, () -> astOf("(")); + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + "\n" + "(\n" + @@ -331,8 +331,8 @@ void alternationWithUnusedEndOptional() { @Test void alternationWithUnusedStartOptional() { - CucumberExpressionParseException exception = assertThrows( - CucumberExpressionParseException.class, + CucumberExpressionException exception = assertThrows( + CucumberExpressionException.class, () -> astOf("three blind\\ mice/rats(")); assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + "\n" + From 0e132c1a1c8ce6a708d0f22544f0f92e2acef415 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 12 Jul 2020 10:33:56 +0200 Subject: [PATCH 074/183] WIP --- cucumber-expressions/java/examples.txt | 2 +- .../io/cucumber/cucumberexpressions/Ast.java | 99 ++- .../CucumberExpression.java | 35 +- .../CucumberExpressionException.java | 61 +- .../CucumberExpressionParser.java | 50 +- .../CucumberExpressionTokenizer.java | 64 +- .../CucumberExpressionParserTest.java | 623 +++++++++--------- .../CucumberExpressionTokenizerTest.java | 148 +++-- gherkin/go/go.mod | 1 - 9 files changed, 603 insertions(+), 480 deletions(-) diff --git a/cucumber-expressions/java/examples.txt b/cucumber-expressions/java/examples.txt index c6acb6958e..35b7bd17a9 100644 --- a/cucumber-expressions/java/examples.txt +++ b/cucumber-expressions/java/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \\[]^$.|?*+ +I have {int} cuke(s) and some \[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 79417d36f2..aa6c40f31e 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Objects; +import java.util.StringJoiner; import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; @@ -14,23 +15,28 @@ static final class AstNode { private final Type type; private final List nodes; private final String token; + private final int startIndex; + private final int endIndex; - AstNode(Type type, String token) { - this(type, null, token); + AstNode(Type type, int startIndex, int endIndex, String token) { + this(type, startIndex, endIndex, null, token); } - AstNode(Type type, AstNode... nodes) { - this(type, asList(nodes)); + AstNode(Type type, int startIndex, int endIndex, AstNode... nodes) { + this(type, startIndex, endIndex, asList(nodes)); } - AstNode(Type type, List nodes) { - this(type, nodes, null); + AstNode(Type type, int startIndex, int endIndex, List nodes) { + this(type, startIndex, endIndex, nodes, null); } - private AstNode(Type type, List nodes, String token) { - this.type = type; + private AstNode(Type type, int startIndex, int endIndex, List nodes, String token) { + this.type = requireNonNull(type); this.nodes = nodes; this.token = token; + this.startIndex = startIndex; + this.endIndex = endIndex; + } enum Type { @@ -42,20 +48,31 @@ enum Type { EXPRESSION_NODE } - List getNodes() { + int start(){ + return startIndex; + } + int end(){ + return endIndex; + } + + List nodes() { return nodes; } - Type getType() { + boolean isLeaf() { + return nodes == null; + } + + Type type() { return type; } - String getText() { - if (token != null) + String text() { + if (isLeaf()) return token; - return getNodes().stream() - .map(AstNode::getText) + return nodes().stream() + .map(AstNode::text) .collect(joining()); } @@ -69,7 +86,7 @@ private StringBuilder toString(int depth) { for (int i = 0; i < depth; i++) { sb.append("\t"); } - sb.append("AstNode{" + "type=").append(type); + sb.append("AstNode{").append(startIndex).append(":").append(endIndex).append(", type=").append(type); if (token != null) { sb.append(", token=").append(token); @@ -92,50 +109,72 @@ private StringBuilder toString(int depth) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; AstNode astNode = (AstNode) o; - return type == astNode.type && + return startIndex == astNode.startIndex && + endIndex == astNode.endIndex && + type == astNode.type && Objects.equals(nodes, astNode.nodes) && Objects.equals(token, astNode.token); } @Override public int hashCode() { - return Objects.hash(type, nodes, token); + return Objects.hash(type, nodes, token, startIndex, endIndex); } + } static final class Token { + final int startIndex; + final int endIndex; final String text; final Token.Type type; - Token(String text, Token.Type type) { - this.text = text; - this.type = type; + Token(String text, Token.Type type, int startIndex, int endIndex) { + this.text = requireNonNull(text); + this.type = requireNonNull(type); + this.startIndex = startIndex; + this.endIndex = endIndex; + } + + int start(){ + return startIndex; + } + int end(){ + return endIndex; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Token token = (Token) o; - return text.equals(token.text) && + return startIndex == token.startIndex && + endIndex == token.endIndex && + text.equals(token.text) && type == token.type; } @Override public int hashCode() { - return Objects.hash(text, type); + return Objects.hash(startIndex, endIndex, text, type); } @Override public String toString() { - return "Token{" + - "text='" + text + '\'' + - ", type=" + type + - '}'; + return new StringJoiner(", ", Token.class.getSimpleName() + "[", "]") + .add("startIndex=" + startIndex) + .add("endIndex=" + endIndex) + .add("text='" + text + "'") + .add("type=" + type) + .toString(); } enum Type { diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index c730f8e825..cbab6dcc74 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -17,7 +17,6 @@ public final class CucumberExpression implements Expression { private static final Pattern ESCAPE_PATTERN = Pattern.compile("([\\\\^\\[({$.|?*+})\\]])"); private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; - private static final String ALTERNATIVE_MAY_NOT_BE_EMPTY = "Alternative may not be empty: "; private static final String OPTIONAL_MAY_NOT_BE_EMPTY = "Optional may not be empty: "; private static final String ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS = "Alternative may not exclusively contain optionals: "; @@ -25,21 +24,22 @@ public final class CucumberExpression implements Expression { private final String source; private final TreeRegexp treeRegexp; private final ParameterTypeRegistry parameterTypeRegistry; + private final AstNode ast; CucumberExpression(String expression, ParameterTypeRegistry parameterTypeRegistry) { this.source = expression; this.parameterTypeRegistry = parameterTypeRegistry; CucumberExpressionParser parser = new CucumberExpressionParser(); - AstNode ast = parser.parse(expression); + this.ast = parser.parse(expression); String pattern = rewriteToRegex(ast); treeRegexp = new TreeRegexp(pattern); } private String rewriteToRegex(AstNode node) { - switch (node.getType()) { + switch (node.type()) { case TEXT_NODE: - return escapeRegex(node.getText()); + return escapeRegex(node.text()); case OPTIONAL_NODE: return rewriteOptional(node); case ALTERNATION_NODE: @@ -51,7 +51,7 @@ private String rewriteToRegex(AstNode node) { case EXPRESSION_NODE: return rewriteExpression(node); default: - throw new IllegalArgumentException(node.getType().name()); + throw new IllegalArgumentException(node.type().name()); } } @@ -62,35 +62,34 @@ private static String escapeRegex(String text) { private String rewriteOptional(AstNode node) { assertNoParameters(node, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); assertNotEmpty(node, OPTIONAL_MAY_NOT_BE_EMPTY); - return node.getNodes().stream() + return node.nodes().stream() .map(this::rewriteToRegex) .collect(joining("", "(?:", ")?")); } - private String rewriteAlternation(AstNode node) { // Make sure the alternative parts aren't empty and don't contain parameter types - for (AstNode alternative : node.getNodes()) { - if (alternative.getNodes().isEmpty()) { - throw new CucumberExpressionException(ALTERNATIVE_MAY_NOT_BE_EMPTY + source); + for (AstNode alternative : node.nodes()) { + if (alternative.nodes().isEmpty()) { + throw CucumberExpressionException.createAlternativeIsEmpty(this.source, ast, alternative); } assertNoParameters(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); assertNotEmpty(alternative, ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS); } - return node.getNodes() + return node.nodes() .stream() .map(this::rewriteToRegex) .collect(joining("|", "(?:", ")")); } private String rewriteAlternative(AstNode node) { - return node.getNodes().stream() + return node.nodes().stream() .map(this::rewriteToRegex) .collect(joining()); } private String rewriteParameter(AstNode node) { - String name = node.getText(); + String name = node.text(); ParameterType.checkParameterTypeName(name); ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); if (parameterType == null) { @@ -106,15 +105,15 @@ private String rewriteParameter(AstNode node) { } private String rewriteExpression(AstNode node) { - return node.getNodes().stream() + return node.nodes().stream() .map(this::rewriteToRegex) .collect(joining("", "^", "$")); } private void assertNotEmpty(AstNode node, String message) { - boolean hasTextNode = node.getNodes() + boolean hasTextNode = node.nodes() .stream() - .map(AstNode::getType) + .map(AstNode::type) .anyMatch(type -> type == TEXT_NODE); if (!hasTextNode) { throw new CucumberExpressionException(message + source); @@ -122,8 +121,8 @@ private void assertNotEmpty(AstNode node, String message) { } private void assertNoParameters(AstNode node, String message) { - boolean hasParameter = node.getNodes().stream() - .map(AstNode::getType) + boolean hasParameter = node.nodes().stream() + .map(AstNode::type) .anyMatch(type -> type == PARAMETER_NODE); if (hasParameter) { throw new CucumberExpressionException(message + source); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 5c72eff997..4b84a238d4 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -1,5 +1,6 @@ package io.cucumber.cucumberexpressions; +import io.cucumber.cucumberexpressions.Ast.AstNode; import io.cucumber.cucumberexpressions.Ast.Token; import io.cucumber.cucumberexpressions.Ast.Token.Type; import org.apiguardian.api.API; @@ -10,6 +11,7 @@ @API(status = API.Status.STABLE) public class CucumberExpressionException extends RuntimeException { + CucumberExpressionException(String message) { super(message); } @@ -18,17 +20,16 @@ public class CucumberExpressionException extends RuntimeException { super(message, cause); } - - static CucumberExpressionException createCaptureGroupParameterTypeMisMatch(TreeRegexp treeRegexp, List> parameterTypes, List argGroups) { - return new CucumberExpressionException(String.format("Expression /%s/ has %s capture groups (%s), but there were %s parameter types (%s)", - treeRegexp.pattern().pattern(), - argGroups.size(), - getGroupValues(argGroups), - parameterTypes.size(), - getParameterTypeNames(parameterTypes) - )); + return new CucumberExpressionException( + String.format("Expression /%s/ has %s capture groups (%s), but there were %s parameter types (%s)", + treeRegexp.pattern().pattern(), + argGroups.size(), + getGroupValues(argGroups), + parameterTypes.size(), + getParameterTypeNames(parameterTypes) + )); } private static List getParameterTypeNames(List> parameterTypes) { @@ -49,14 +50,17 @@ private static List getGroupValues(List argGroups) { return list; } - static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List tokens, int current) { - return new CucumberExpressionException( + static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, + List tokens, int current) { + return new CucumberExpressionException("" + "This Cucumber Expression has problem:" + "\n" + - "\n" + - expressionOf(tokens) + "\n" + - pointAtCurrentToken(tokens, current) + "\n" + - "The '" + beginToken.symbol() + "' at " + pointAtCurrentToken(tokens, current).length() + " did not have a matching '" + endToken.symbol() + "'. " + "\n" + - "If you did not intended to use " + beginToken.purpose() + " you can use '\\" + beginToken.symbol() + "' to escape the " + beginToken.purpose() + "\n"); + "\n" + + expressionOf(tokens) + "\n" + + pointAtCurrentToken(tokens, current) + "\n" + + "The '" + beginToken.symbol() + "' at " + pointAtCurrentToken(tokens, current) + .length() + " did not have a matching '" + endToken.symbol() + "'. " + "\n" + + "If you did not intended to use " + beginToken.purpose() + " you can use '\\" + beginToken + .symbol() + "' to escape the " + beginToken.purpose() + "\n"); } private static String expressionOf(List expression) { @@ -64,13 +68,34 @@ private static String expressionOf(List expression) { } private static StringBuilder pointAtCurrentToken(List expression, int current) { - int currentInExpr = expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); + int indexInExpression = expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); + return pointAt(indexInExpression); + } + + static CucumberExpressionException createTheEndOfLineCanNotBeEscapedException(String expression) { + return new CucumberExpressionException("" + + "This Cucumber Expression has problem:\n" + + "\n" + + expression + "\n" + + pointAt(expression.length()) + "\n" + + "You can use '\\\\' to escape the the '\\'"); + } + + private static StringBuilder pointAt(int index) { StringBuilder pointer = new StringBuilder(); - for (int i = 0; i < currentInExpr; i++) { + for (int i = 0; i < index; i++) { pointer.append(" "); } pointer.append("^"); return pointer; } + static CucumberExpressionException createAlternativeIsEmpty(String expression, AstNode ast, AstNode node) { + return new CucumberExpressionException("This Cucumber Expression has problem:" + "\n" + + "\n" + + expression + "\n" + + pointAt(0) + "\n" + //TODO: 0 + "Alternative may not be empty: " +""); + } + } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index da1fc9d780..db2533a22c 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -33,7 +33,7 @@ final class CucumberExpressionParser { */ private static final Parser textParser = (expression, current) -> { Token token = expression.get(current); - return new Result(1, new AstNode(TEXT_NODE, token.text)); + return new Result(1, new AstNode(TEXT_NODE, token.start(), token.end(), token.text)); }; /* @@ -57,17 +57,13 @@ final class CucumberExpressionParser { asList(parameterParser, textParser) ); - // Marker. This way we don't need to model the - // the tail end of alternation in the AST: - // // alternation := alternative* + ( '/' + alternative* )+ - private static final AstNode ALTERNATIVE_SEPARATOR = new AstNode(ALTERNATIVE_NODE, "/"); - private static final Parser alternativeSeparator = (expression, current) -> { if (!lookingAt(expression, current, ALTERNATION)) { return new Result(0); } - return new Result(1, ALTERNATIVE_SEPARATOR); + Token token = expression.get(current); + return new Result(1, new AstNode(ALTERNATIVE_NODE, token.start(), token.end(), "/")); }; private static final List alternativeParsers = asList( @@ -89,12 +85,14 @@ final class CucumberExpressionParser { } Result result = parseTokensUntil(alternativeParsers, expression, current, WHITE_SPACE, END_OF_LINE); - if (!result.ast.contains(ALTERNATIVE_SEPARATOR)) { + int subCurrent = current + result.consumed; + if (result.ast.stream().noneMatch(astNode -> astNode.type() == ALTERNATIVE_NODE)) { return new Result(0); } - + int start = expression.get(current).start(); + int end = expression.get(subCurrent).start(); // Does not consume right hand boundary token - return new Result(result.consumed, new AstNode(ALTERNATION_NODE, splitAlternatives(result.ast))); + return new Result(result.consumed, new AstNode(ALTERNATION_NODE, start, end, splitAlternatives(start, end, result.ast))); }; /* @@ -154,7 +152,9 @@ private static Parser parseBetween( throw createMissingEndTokenException(beginToken, endToken, expression, current); } // consumes endToken - return new Result(subCurrent + 1 - current, new AstNode(type, result.ast)); + int start = expression.get(current).start(); + int end = expression.get(subCurrent).end(); + return new Result(subCurrent + 1 - current, new AstNode(type, start, end, result.ast)); }; } @@ -214,19 +214,35 @@ private static boolean lookingAt(List expression, int at, Type token) { return expression.get(at).type == token; } - private static List splitAlternatives(List astNode) { - List alternatives = new ArrayList<>(); + private static List splitAlternatives(int start, int end, List astNode) { + + List seperators = new ArrayList<>(); + List> alternatives = new ArrayList<>(); List alternative = new ArrayList<>(); for (AstNode token : astNode) { - if (ALTERNATIVE_SEPARATOR.equals(token)) { - alternatives.add(new AstNode(ALTERNATIVE_NODE, alternative)); + if (ALTERNATIVE_NODE.equals(token.type())) { + seperators.add(token); + alternatives.add(alternative); alternative = new ArrayList<>(); } else { alternative.add(token); } } - alternatives.add(new AstNode(ALTERNATIVE_NODE, alternative)); - return alternatives; + alternatives.add(alternative); + + List alts = new ArrayList<>(); + for (int i = 0; i < alternatives.size(); i++) { + List astNodes = alternatives.get(i); + if (i == 0) { + alts.add(new AstNode(ALTERNATIVE_NODE, start, seperators.get(i).start(), astNodes)); + } else if( i == alternatives.size() - 1){ + alts.add(new AstNode(ALTERNATIVE_NODE, seperators.get(i - 1).end(), end, astNodes)); + } else { + alts.add(new AstNode(ALTERNATIVE_NODE, seperators.get(i-1).end(), seperators.get(i).start(), astNodes)); + } + } + + return alts; } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 447b6a7512..8b6d643d65 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -9,9 +9,11 @@ import java.util.NoSuchElementException; import java.util.PrimitiveIterator.OfInt; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createTheEndOfLineCanNotBeEscapedException; + final class CucumberExpressionTokenizer { - List tokenize(String expression){ + List tokenize(String expression) { List tokens = new ArrayList<>(); tokenizeImpl(expression).forEach(tokens::add); return tokens; @@ -24,6 +26,8 @@ private Iterable tokenizeImpl(String expression) { Type previousTokenType = null; Type currentTokenType = Type.START_OF_LINE; boolean treatAsText = false; + int index = 0; + int escaped = 0; @Override public boolean hasNext() { @@ -35,49 +39,65 @@ public Token next() { if (!hasNext()) { throw new NoSuchElementException(); } - - if(currentTokenType == Type.START_OF_LINE){ - currentTokenType = null; - return new Token("", Type.START_OF_LINE); + if (currentTokenType == Type.START_OF_LINE) { + Token token = convertBufferToToken(currentTokenType); + advanceTokenTypes(); + return token; } while (codePoints.hasNext()) { - int current = codePoints.nextInt(); - if (!treatAsText && current == '\\') { + int token = codePoints.nextInt(); + if (!treatAsText && token == '\\') { + escaped++; treatAsText = true; continue; } - currentTokenType = tokenTypeOf(current, treatAsText); + currentTokenType = tokenTypeOf(token, treatAsText); treatAsText = false; - if (previousTokenType != null + if (previousTokenType != Type.START_OF_LINE && (currentTokenType != previousTokenType || (currentTokenType != Type.WHITE_SPACE && currentTokenType != Type.TEXT))) { - Token t = new Token(buffer.toString(), previousTokenType); - buffer = new StringBuilder(); - buffer.appendCodePoint(current); - previousTokenType = currentTokenType; + Token t = convertBufferToToken(previousTokenType); + advanceTokenTypes(); + buffer.appendCodePoint(token); return t; + } else { + advanceTokenTypes(); + buffer.appendCodePoint(token); } - buffer.appendCodePoint(current); - previousTokenType = currentTokenType; } if (buffer.length() > 0) { - Token t = new Token(buffer.toString(), previousTokenType); - buffer = new StringBuilder(); - currentTokenType = Type.END_OF_LINE; - return t; + Token token = convertBufferToToken(previousTokenType); + advanceTokenTypes(); + return token; } + currentTokenType = Type.END_OF_LINE; if (treatAsText) { - throw new CucumberExpressionException("End of line can not be escaped"); + throw createTheEndOfLineCanNotBeEscapedException(expression); } + Token token = convertBufferToToken(currentTokenType); + advanceTokenTypes(); + return token; + } + private void advanceTokenTypes() { + previousTokenType = currentTokenType; currentTokenType = null; - previousTokenType = Type.END_OF_LINE; - Token t = new Token(buffer.toString(), previousTokenType); + } + + private Token convertBufferToToken(Type currentTokenType) { + int escapeTokens = 0; + if(currentTokenType == Type.TEXT){ + escapeTokens = escaped; + escaped = 0; + } + int endIndex = index + buffer.codePointCount(0, buffer.length()) + escapeTokens; + Token t = new Token(buffer.toString(), currentTokenType, index, endIndex); buffer = new StringBuilder(); + this.index = endIndex; return t; } }; diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 159a39d8a0..575a9becaa 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -1,6 +1,7 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.Ast.AstNode; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATION_NODE; @@ -13,6 +14,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; class CucumberExpressionParserTest { @@ -21,19 +23,19 @@ class CucumberExpressionParserTest { @Test void emptyString() { assertThat(astOf(""), equalTo( - new AstNode(EXPRESSION_NODE) + new AstNode(EXPRESSION_NODE, 0, 0) )); } @Test void phrase() { assertThat(astOf("three blind mice"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "three"), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "blind"), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "mice") + new AstNode(EXPRESSION_NODE, 0, 16, + new AstNode(TEXT_NODE, 0, 5, "three"), + new AstNode(TEXT_NODE, 5, 6, " "), + new AstNode(TEXT_NODE, 6, 11, "blind"), + new AstNode(TEXT_NODE, 11, 12, " "), + new AstNode(TEXT_NODE, 12, 16, "mice") ) )); } @@ -41,9 +43,9 @@ void phrase() { @Test void optional() { assertThat(astOf("(blind)"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, "blind") + new AstNode(EXPRESSION_NODE, 0, 7, + new AstNode(OPTIONAL_NODE, 0, 7, + new AstNode(TEXT_NODE, 1, 6, "blind") ) ) )); @@ -52,9 +54,9 @@ void optional() { @Test void parameter() { assertThat(astOf("{string}"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(PARAMETER_NODE, - new AstNode(TEXT_NODE, "string") + new AstNode(EXPRESSION_NODE, 0, 8, + new AstNode(PARAMETER_NODE, 0, 8, + new AstNode(TEXT_NODE, 1, 7, "string") ) ) )); @@ -63,306 +65,311 @@ void parameter() { @Test void anonymousParameter() { assertThat(astOf("{}"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(PARAMETER_NODE) + new AstNode(EXPRESSION_NODE, 0, 2, + new AstNode(PARAMETER_NODE, 0, 2) ) )); } - @Test - void optionalPhrase() { - assertThat(astOf("three (blind) mice"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "three"), - new AstNode(TEXT_NODE, " "), - new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, "blind") - ), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "mice") - ) - )); - } - - @Test - void escapedEndOfLine() { - // TODO: Better error message - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\")); - assertThat(exception.getMessage(), is("End of line can not be escaped")); - } - - @Test - void escapedBackSlash() { - assertThat(astOf("\\\\"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "\\") - ) - )); - } - - @Test - void openingBrace() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + - "\n" + - "{\n" + - "^\n" + - "The '{' at 1 did not have a matching '}'. \n" + - "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n" - )); - } - - @Test - void closingBrace() { - assertThat(astOf("}"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "}") - ) - )); - } - - @Test - void unfinishedParameter() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + - "\n" + - "{string\n" + - "^\n" + - "The '{' at 1 did not have a matching '}'. \n" + - "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n")); - } - - @Test - void openingParenthesis() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + - "\n" + - "(\n" + - "^\n" + - "The '(' at 1 did not have a matching ')'. \n" + - "If you did not intended to use optional text you can use '\\(' to escape the optional text\n" - )); - } - - @Test - void closingParenthesis() { - assertThat(astOf(")"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, ")") - ) - )); - } - - @Test - void escapedOpeningParenthesis() { - assertThat(astOf("\\("), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "(") - ) - )); - } - - @Test - void escapedOptional() { - assertThat(astOf("\\(blind)"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "(blind"), - new AstNode(TEXT_NODE, ")") - ) - )); - } - - @Test - void escapedOptionalPhrase() { - assertThat(astOf("three \\(blind) mice"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "three"), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "(blind"), - new AstNode(TEXT_NODE, ")"), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "mice") - ) - )); - } - - @Test - void escapedOptionalFollowedByOptional() { - assertThat(astOf("three \\((very) blind) mice"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "three"), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "("), - new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, "very") - ), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "blind"), - new AstNode(TEXT_NODE, ")"), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "mice") - ) - )); - } - - @Test - void optionalContainingEscapedOptional() { - assertThat(astOf("three ((very\\) blind) mice"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "three"), - new AstNode(TEXT_NODE, " "), - new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, "("), - new AstNode(TEXT_NODE, "very)"), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "blind") - ), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "mice") - ) - )); - } - - @Test - void alternation() { - assertThat(astOf("mice/rats"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(ALTERNATION_NODE, - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, "mice") - ), - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, "rats") - ) - ) - ) - )); - } - - @Test - void emptyAlternation() { - assertThat(astOf("/"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(ALTERNATION_NODE, - new AstNode(ALTERNATIVE_NODE), - new AstNode(ALTERNATIVE_NODE) - ) - ) - )); - } - - @Test - void emptyAlternations() { - assertThat(astOf("//"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(ALTERNATION_NODE, - new AstNode(ALTERNATIVE_NODE), - new AstNode(ALTERNATIVE_NODE), - new AstNode(ALTERNATIVE_NODE) - ) - ) - )); - } - - @Test - void escapedAlternation() { - assertThat(astOf("mice\\/rats"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "mice/rats") - ) - )); - } - - @Test - void alternationPhrase() { - assertThat(astOf("three hungry/blind mice"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "three"), - new AstNode(TEXT_NODE, " "), - new AstNode(ALTERNATION_NODE, - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, "hungry") - ), - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, "blind") - ) - ), - new AstNode(TEXT_NODE, " "), - new AstNode(TEXT_NODE, "mice") - ) - )); - } - - @Test - void alternationWithWhiteSpace() { - assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(ALTERNATION_NODE, - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, " three hungry") - ), - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, "blind mice ") - ) - ) - - ) - )); - } - - @Test - void alternationWithUnusedEndOptional() { - assertThat(astOf("three )blind\\ mice/rats"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "three"), - new AstNode(TEXT_NODE, " "), - new AstNode(ALTERNATION_NODE, - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, ")"), - new AstNode(TEXT_NODE, "blind mice") - ), - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, "rats") - ) - ) - ) - )); - } - - @Test - void alternationWithUnusedStartOptional() { - CucumberExpressionException exception = assertThrows( - CucumberExpressionException.class, - () -> astOf("three blind\\ mice/rats(")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + - "\n" + - "three blind mice/rats(\n" + - " ^\n" + - "The '(' at 22 did not have a matching ')'. \n" + - "If you did not intended to use optional text you can use '\\(' to escape the optional text\n")); - } - - @Test - void alternationFollowedByOptional() { - assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( - new AstNode(EXPRESSION_NODE, - new AstNode(TEXT_NODE, "three"), - new AstNode(TEXT_NODE, " "), - new AstNode(ALTERNATION_NODE, - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, "blind rat") - ), - new AstNode(ALTERNATIVE_NODE, - new AstNode(TEXT_NODE, "cat"), - new AstNode(OPTIONAL_NODE, - new AstNode(TEXT_NODE, "s") - ) - ) - ) - ) - )); - } + @Test + void optionalPhrase() { + assertThat(astOf("three (blind) mice"), equalTo( + new AstNode(EXPRESSION_NODE,0,18, + new AstNode(TEXT_NODE, 0, 5, "three"), + new AstNode(TEXT_NODE, 5, 6, " "), + new AstNode(OPTIONAL_NODE, 6, 13, + new AstNode(TEXT_NODE, 7, 12, "blind") + ), + new AstNode(TEXT_NODE, 13, 14, " "), + new AstNode(TEXT_NODE, 14, 18, "mice") + ) + )); + } + + @Test + void escapedEndOfLine() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + "\n" + + "\\\n" + + " ^\n" + + "You can use '\\\\' to escape the the '\\'")); + fail(); + } + + @Test + void escapedBackSlash() { + assertThat(astOf("\\\\"), equalTo( + new AstNode(EXPRESSION_NODE,0,2, + new AstNode(TEXT_NODE, 0, 2, "\\") + ) + )); + } + + @Test + void openingBrace() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + "\n" + + "{\n" + + "^\n" + + "The '{' at 1 did not have a matching '}'. \n" + + "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n" + )); + } + + @Test + void closingBrace() { + assertThat(astOf("}"), equalTo( + new AstNode(EXPRESSION_NODE,0,1, + new AstNode(TEXT_NODE, 0, 1, "}") + ) + )); + } + + @Test + void unfinishedParameter() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + "\n" + + "{string\n" + + "^\n" + + "The '{' at 1 did not have a matching '}'. \n" + + "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n")); + } + + @Test + void openingParenthesis() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + "\n" + + "(\n" + + "^\n" + + "The '(' at 1 did not have a matching ')'. \n" + + "If you did not intended to use optional text you can use '\\(' to escape the optional text\n" + )); + } + + @Test + void closingParenthesis() { + assertThat(astOf(")"), equalTo( + new AstNode(EXPRESSION_NODE,0,1, + new AstNode(TEXT_NODE, 0, 1, ")") + ) + )); + } + + @Test + void escapedOpeningParenthesis() { + assertThat(astOf("\\("), equalTo( + new AstNode(EXPRESSION_NODE,0,2, + new AstNode(TEXT_NODE, 0, 2, "(") + ) + )); + } + + @Test + void escapedOptional() { + assertThat(astOf("\\(blind)"), equalTo( + new AstNode(EXPRESSION_NODE,0,8, + new AstNode(TEXT_NODE, 0, 7, "(blind"), + new AstNode(TEXT_NODE, 7, 8, ")") + ) + )); + } + + @Test + void escapedOptionalPhrase() { + assertThat(astOf("three \\(blind) mice"), equalTo( + new AstNode(EXPRESSION_NODE,0,19, + new AstNode(TEXT_NODE, 0, 5, "three"), + new AstNode(TEXT_NODE, 5, 6, " "), + new AstNode(TEXT_NODE, 6, 13, "(blind"), + new AstNode(TEXT_NODE, 13, 14, ")"), + new AstNode(TEXT_NODE, 14, 15, " "), + new AstNode(TEXT_NODE, 15, 19, "mice") + ) + )); + } + + @Test + void escapedOptionalFollowedByOptional() { + assertThat(astOf("three \\((very) blind) mice"), equalTo( + new AstNode(EXPRESSION_NODE, 0, 26, + new AstNode(TEXT_NODE, 0, 5, "three"), + new AstNode(TEXT_NODE, 5, 6, " "), + new AstNode(TEXT_NODE, 6, 8, "("), + new AstNode(OPTIONAL_NODE, 8, 14, + new AstNode(TEXT_NODE, 9, 13, "very") + ), + new AstNode(TEXT_NODE, 14, 15, " "), + new AstNode(TEXT_NODE, 15, 20, "blind"), + new AstNode(TEXT_NODE, 20, 21, ")"), + new AstNode(TEXT_NODE, 21, 22, " "), + new AstNode(TEXT_NODE, 22, 26, "mice") + ) + )); + } + + @Test + void optionalContainingEscapedOptional() { + assertThat(astOf("three ((very\\) blind) mice"), equalTo( + new AstNode(EXPRESSION_NODE, 0, 26, + new AstNode(TEXT_NODE, 0, 5, "three"), + new AstNode(TEXT_NODE, 5, 6, " "), + new AstNode(OPTIONAL_NODE, 6,21, + new AstNode(TEXT_NODE, 7, 8, "("), + new AstNode(TEXT_NODE, 8, 14, "very)"), + new AstNode(TEXT_NODE, 14, 15, " "), + new AstNode(TEXT_NODE, 15, 20, "blind") + ), + new AstNode(TEXT_NODE, 21, 22, " "), + new AstNode(TEXT_NODE, 22, 26, "mice") + ) + )); + } + + @Test + void alternation() { + assertThat(astOf("mice/rats"), equalTo( + new AstNode(EXPRESSION_NODE, 0,9, + new AstNode(ALTERNATION_NODE, 0,9, + new AstNode(ALTERNATIVE_NODE,0,4, + new AstNode(TEXT_NODE, 0, 4, "mice") + ), + new AstNode(ALTERNATIVE_NODE, 5,9, + new AstNode(TEXT_NODE, 5, 9, "rats") + ) + ) + ) + )); + } + + @Test + void emptyAlternation() { + assertThat(astOf("/"), equalTo( + new AstNode(EXPRESSION_NODE,0,1, + new AstNode(ALTERNATION_NODE,0,1, + new AstNode(ALTERNATIVE_NODE, 0,0), + new AstNode(ALTERNATIVE_NODE, 1,1) + ) + ) + )); + } + + @Test + void emptyAlternations() { + assertThat(astOf("//"), equalTo( + new AstNode(EXPRESSION_NODE,0,2, + new AstNode(ALTERNATION_NODE, 0,2, + new AstNode(ALTERNATIVE_NODE, 0,0), + new AstNode(ALTERNATIVE_NODE, 1,1), + new AstNode(ALTERNATIVE_NODE, 2,2) + ) + ) + )); + } + + // @Test + // void escapedAlternation() { + // assertThat(astOf("mice\\/rats"), equalTo( + // new AstNode(EXPRESSION_NODE, + // new AstNode(TEXT_NODE, -1, -1, "mice/rats") + // ) + // )); + // } + // + // @Test + // void alternationPhrase() { + // assertThat(astOf("three hungry/blind mice"), equalTo( + // new AstNode(EXPRESSION_NODE, + // new AstNode(TEXT_NODE, -1, -1, "three"), + // new AstNode(TEXT_NODE, -1, -1, " "), + // new AstNode(ALTERNATION_NODE, + // new AstNode(ALTERNATIVE_NODE, + // new AstNode(TEXT_NODE, -1, -1, "hungry") + // ), + // new AstNode(ALTERNATIVE_NODE, + // new AstNode(TEXT_NODE, -1, -1, "blind") + // ) + // ), + // new AstNode(TEXT_NODE, -1, -1, " "), + // new AstNode(TEXT_NODE, -1, -1, "mice") + // ) + // )); + // } + // + // @Test + // void alternationWithWhiteSpace() { + // assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( + // new AstNode(EXPRESSION_NODE, + // new AstNode(ALTERNATION_NODE, + // new AstNode(ALTERNATIVE_NODE, + // new AstNode(TEXT_NODE, -1, -1, " three hungry") + // ), + // new AstNode(ALTERNATIVE_NODE, + // new AstNode(TEXT_NODE, -1, -1, "blind mice ") + // ) + // ) + // + // ) + // )); + // } + // + // @Test + // void alternationWithUnusedEndOptional() { + // assertThat(astOf("three )blind\\ mice/rats"), equalTo( + // new AstNode(EXPRESSION_NODE, + // new AstNode(TEXT_NODE, -1, -1, "three"), + // new AstNode(TEXT_NODE, -1, -1, " "), + // new AstNode(ALTERNATION_NODE, + // new AstNode(ALTERNATIVE_NODE, + // new AstNode(TEXT_NODE, -1, -1, ")"), + // new AstNode(TEXT_NODE, -1, -1, "blind mice") + // ), + // new AstNode(ALTERNATIVE_NODE, + // new AstNode(TEXT_NODE, -1, -1, "rats") + // ) + // ) + // ) + // )); + // } + // + // @Test + // void alternationWithUnusedStartOptional() { + // CucumberExpressionException exception = assertThrows( + // CucumberExpressionException.class, + // () -> astOf("three blind\\ mice/rats(")); + // assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + // "\n" + + // "three blind mice/rats(\n" + + // " ^\n" + + // "The '(' at 22 did not have a matching ')'. \n" + + // "If you did not intended to use optional text you can use '\\(' to escape the optional text\n")); + // } + // + // @Test + // void alternationFollowedByOptional() { + // assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( + // new AstNode(EXPRESSION_NODE, + // new AstNode(TEXT_NODE, -1, -1, "three"), + // new AstNode(TEXT_NODE, -1, -1, " "), + // new AstNode(ALTERNATION_NODE, + // new AstNode(ALTERNATIVE_NODE, + // new AstNode(TEXT_NODE, -1, -1, "blind rat") + // ), + // new AstNode(ALTERNATIVE_NODE, + // new AstNode(TEXT_NODE, -1, -1, "cat"), + // new AstNode(OPTIONAL_NODE, + // new AstNode(TEXT_NODE, -1, -1, "s") + // ) + // ) + // ) + // ) + // )); + // } + // private AstNode astOf(String expression) { return parser.parse(expression); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 0c5dc86ab6..f894256821 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -1,6 +1,5 @@ package io.cucumber.cucumberexpressions; - import io.cucumber.cucumberexpressions.Ast.Token; import org.junit.jupiter.api.Test; @@ -20,133 +19,152 @@ class CucumberExpressionTokenizerTest { private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); - @Test void emptyString() { assertThat(tokenizer.tokenize(""), contains( - new Token("", START_OF_LINE), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("", END_OF_LINE, 0, 0) )); } @Test void phrase() { assertThat(tokenizer.tokenize("three blind mice"), contains( - new Token("", START_OF_LINE), - new Token("three", TEXT), - new Token(" ", WHITE_SPACE), - new Token("blind", TEXT), - new Token(" ", WHITE_SPACE), - new Token("mice", TEXT), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("three", TEXT, 0, 5), + new Token(" ", WHITE_SPACE, 5, 6), + new Token("blind", TEXT, 6, 11), + new Token(" ", WHITE_SPACE, 11, 12), + new Token("mice", TEXT, 12, 16), + new Token("", END_OF_LINE, 16, 16) )); } @Test void optional() { assertThat(tokenizer.tokenize("(blind)"), contains( - new Token("", START_OF_LINE), - new Token("(", BEGIN_OPTIONAL), - new Token("blind", TEXT), - new Token(")", END_OPTIONAL), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("(", BEGIN_OPTIONAL, 0, 1), + new Token("blind", TEXT, 1, 6), + new Token(")", END_OPTIONAL, 6, 7), + new Token("", END_OF_LINE, 7, 7) )); } @Test void escapedOptional() { assertThat(tokenizer.tokenize("\\(blind\\)"), contains( - new Token("", START_OF_LINE), - new Token("(blind)", TEXT), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("(blind)", TEXT, 0, 9), + new Token("", END_OF_LINE, 9, 9) )); } @Test void optionalPhrase() { assertThat(tokenizer.tokenize("three (blind) mice"), contains( - new Token("", START_OF_LINE), - new Token("three", TEXT), - new Token(" ", WHITE_SPACE), - new Token("(", BEGIN_OPTIONAL), - new Token("blind", TEXT), - new Token(")", END_OPTIONAL), - new Token(" ", WHITE_SPACE), - new Token("mice", TEXT), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("three", TEXT, 0, 5), + new Token(" ", WHITE_SPACE, 5, 6), + new Token("(", BEGIN_OPTIONAL, 6, 7), + new Token("blind", TEXT, 7, 12), + new Token(")", END_OPTIONAL, 12, 13), + new Token(" ", WHITE_SPACE, 13, 14), + new Token("mice", TEXT, 14, 18), + new Token("", END_OF_LINE, 18, 18) )); } @Test void parameter() { assertThat(tokenizer.tokenize("{string}"), contains( - new Token("", START_OF_LINE), - new Token("{", BEGIN_PARAMETER), - new Token("string", TEXT), - new Token("}", END_PARAMETER), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("{", BEGIN_PARAMETER, 0, 1), + new Token("string", TEXT, 1, 7), + new Token("}", END_PARAMETER, 7, 8), + new Token("", END_OF_LINE, 8, 8) )); } @Test void escapedParameter() { assertThat(tokenizer.tokenize("\\{string\\}"), contains( - new Token("", START_OF_LINE), - new Token("{string}", TEXT), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("{string}", TEXT, 0, 10), + new Token("", END_OF_LINE, 10, 10) )); } @Test void parameterPhrase() { assertThat(tokenizer.tokenize("three {string} mice"), contains( - new Token("", START_OF_LINE), - new Token("three", TEXT), - new Token(" ", WHITE_SPACE), - new Token("{", BEGIN_PARAMETER), - new Token("string", TEXT), - new Token("}", END_PARAMETER), - new Token(" ", WHITE_SPACE), - new Token("mice", TEXT), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("three", TEXT, 0, 5), + new Token(" ", WHITE_SPACE, 5, 6), + new Token("{", BEGIN_PARAMETER, 6, 7), + new Token("string", TEXT, 7, 13), + new Token("}", END_PARAMETER, 13, 14), + new Token(" ", WHITE_SPACE, 14, 15), + new Token("mice", TEXT, 15, 19), + new Token("", END_OF_LINE, 19, 19) )); } - @Test void alternation() { assertThat(tokenizer.tokenize("blind/cripple"), contains( - new Token("", START_OF_LINE), - new Token("blind", TEXT), - new Token("/", ALTERNATION), - new Token("cripple", TEXT), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("blind", TEXT, 0, 5), + new Token("/", ALTERNATION, 5, 6), + new Token("cripple", TEXT, 6, 13), + new Token("", END_OF_LINE, 13, 13) )); } @Test void escapedAlternation() { assertThat(tokenizer.tokenize("blind\\ and\\ famished\\/cripple mice"), contains( - new Token("", START_OF_LINE), - new Token("blind and famished/cripple", TEXT), - new Token(" ", WHITE_SPACE), - new Token("mice", TEXT), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("blind and famished/cripple", TEXT, 0, 29), + new Token(" ", WHITE_SPACE, 29, 30), + new Token("mice", TEXT, 30, 34), + new Token("", END_OF_LINE, 34, 34) + )); + } + + @Test + void escapeCharIsStartIndexOfTextToken() { + assertThat(tokenizer.tokenize(" \\/ "), contains( + new Token("", START_OF_LINE, 0, 0), + new Token(" ", WHITE_SPACE, 0, 1), + new Token("/", TEXT, 1, 3), + new Token(" ", WHITE_SPACE, 3, 4), + new Token("", END_OF_LINE, 4, 4) )); } @Test void alternationPhrase() { assertThat(tokenizer.tokenize("three blind/cripple mice"), contains( - new Token("", START_OF_LINE), - new Token("three", TEXT), - new Token(" ", WHITE_SPACE), - new Token("blind", TEXT), - new Token("/", ALTERNATION), - new Token("cripple", TEXT), - new Token(" ", WHITE_SPACE), - new Token("mice", TEXT), - new Token("", END_OF_LINE) + new Token("", START_OF_LINE, 0, 0), + new Token("three", TEXT, 0, 5), + new Token(" ", WHITE_SPACE, 5, 6), + new Token("blind", TEXT, 6, 11), + new Token("/", ALTERNATION, 11, 12), + new Token("cripple", TEXT, 12, 19), + new Token(" ", WHITE_SPACE, 19, 20), + new Token("mice", TEXT, 20, 24), + new Token("", END_OF_LINE, 24, 24) + )); + } + + @Test + void escapedSpace() { + assertThat(tokenizer.tokenize("\\ "), contains( + new Token("", START_OF_LINE, 0, 0), + new Token(" ", TEXT, 0, 2), + new Token("", END_OF_LINE, 2, 2) )); } + } diff --git a/gherkin/go/go.mod b/gherkin/go/go.mod index b332b14ef5..2bf23c5f0a 100644 --- a/gherkin/go/go.mod +++ b/gherkin/go/go.mod @@ -3,7 +3,6 @@ module github.com/cucumber/gherkin-go/v14 require ( github.com/aslakhellesoy/gox v1.0.100 // indirect github.com/cucumber/messages-go/v12 v12.1.1 - github.com/gofrs/uuid v3.3.0+incompatible // indirect github.com/gogo/protobuf v1.3.1 github.com/stretchr/testify v1.6.1 ) From b344af8ad75c6d946ab3ee05dc5739af1a13377b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 15 Jul 2020 22:10:18 +0200 Subject: [PATCH 075/183] Nail down error messages --- cucumber-expressions/examples.txt | 2 +- cucumber-expressions/go/examples.txt | 2 +- cucumber-expressions/java/examples.txt | 2 +- .../CucumberExpression.java | 3 +- .../CucumberExpressionException.java | 42 ++-- .../CucumberExpressionTokenizer.java | 63 +++-- .../CucumberExpressionParserTest.java | 225 +++++++++--------- .../CucumberExpressionTest.java | 6 +- .../ExpressionFactoryTest.java | 2 +- cucumber-expressions/javascript/examples.txt | 2 +- cucumber-expressions/ruby/examples.txt | 2 +- 11 files changed, 196 insertions(+), 155 deletions(-) diff --git a/cucumber-expressions/examples.txt b/cucumber-expressions/examples.txt index 35b7bd17a9..c6acb6958e 100644 --- a/cucumber-expressions/examples.txt +++ b/cucumber-expressions/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \[]^$.|?*+ +I have {int} cuke(s) and some \\[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- diff --git a/cucumber-expressions/go/examples.txt b/cucumber-expressions/go/examples.txt index 35b7bd17a9..c6acb6958e 100644 --- a/cucumber-expressions/go/examples.txt +++ b/cucumber-expressions/go/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \[]^$.|?*+ +I have {int} cuke(s) and some \\[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- diff --git a/cucumber-expressions/java/examples.txt b/cucumber-expressions/java/examples.txt index 35b7bd17a9..c6acb6958e 100644 --- a/cucumber-expressions/java/examples.txt +++ b/cucumber-expressions/java/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \[]^$.|?*+ +I have {int} cuke(s) and some \\[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index cbab6dcc74..9053ec8788 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -10,6 +10,7 @@ import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeIsEmpty; import static java.util.stream.Collectors.joining; @API(status = API.Status.STABLE) @@ -71,7 +72,7 @@ private String rewriteAlternation(AstNode node) { // Make sure the alternative parts aren't empty and don't contain parameter types for (AstNode alternative : node.nodes()) { if (alternative.nodes().isEmpty()) { - throw CucumberExpressionException.createAlternativeIsEmpty(this.source, ast, alternative); + throw createAlternativeIsEmpty(this.source, alternative); } assertNoParameters(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); assertNotEmpty(alternative, ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 4b84a238d4..113c4552af 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -52,33 +52,35 @@ private static List getGroupValues(List argGroups) { static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List tokens, int current) { + int index = currentTokenIndex(tokens, current); return new CucumberExpressionException("" + - "This Cucumber Expression has problem:" + "\n" + + thisCucumberExpressionHasAProblemAt(index) + "\n" + expressionOf(tokens) + "\n" + - pointAtCurrentToken(tokens, current) + "\n" + - "The '" + beginToken.symbol() + "' at " + pointAtCurrentToken(tokens, current) - .length() + " did not have a matching '" + endToken.symbol() + "'. " + "\n" + + pointAt(index) + "\n" + + "The '" + beginToken.symbol() + "' does not have a matching '" + endToken.symbol() + "'. " + "\n" + "If you did not intended to use " + beginToken.purpose() + " you can use '\\" + beginToken .symbol() + "' to escape the " + beginToken.purpose() + "\n"); } + private static String thisPRo(int i) { + return "This Cucumber Expression has problem at index '" + i + "' :" + "\n"; + } + private static String expressionOf(List expression) { return expression.stream().map(token -> token.text).collect(Collectors.joining()); } - private static StringBuilder pointAtCurrentToken(List expression, int current) { - int indexInExpression = expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); - return pointAt(indexInExpression); + private static int currentTokenIndex(List expression, int current) { + return expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); } static CucumberExpressionException createTheEndOfLineCanNotBeEscapedException(String expression) { - return new CucumberExpressionException("" + - "This Cucumber Expression has problem:\n" + + return new CucumberExpressionException(thisCucumberExpressionHasAProblemAt(expression.length()) + "\n" + expression + "\n" + pointAt(expression.length()) + "\n" + - "You can use '\\\\' to escape the the '\\'"); + "The end of line can not be escaped. You can use '\\\\' to escape the the '\\'"); } private static StringBuilder pointAt(int index) { @@ -90,12 +92,24 @@ private static StringBuilder pointAt(int index) { return pointer; } - static CucumberExpressionException createAlternativeIsEmpty(String expression, AstNode ast, AstNode node) { - return new CucumberExpressionException("This Cucumber Expression has problem:" + "\n" + + static CucumberExpressionException createAlternativeIsEmpty(String expression, AstNode node) { + return new CucumberExpressionException(thisCucumberExpressionHasAProblemAt(node.start()) + + "\n" + + expression + "\n" + + pointAt(node.start()) + "\n" + + "Alternative may not be empty. If you did not mean to use an alternative you can use '\\\\' to escape the the '\\'"); + } + + private static String thisCucumberExpressionHasAProblemAt(int index) { + return "This Cucumber Expression has problem at column " + (index+1) + ":" + "\n"; + } + + static CucumberExpressionException createCantEscape(String expression, int index) { + return new CucumberExpressionException(thisCucumberExpressionHasAProblemAt(index) + "\n" + expression + "\n" + - pointAt(0) + "\n" + //TODO: 0 - "Alternative may not be empty: " +""); + pointAt(index) + "\n" + + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped. If you did mean to use an '\\' you can use '\\\\' to escape it"); } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 8b6d643d65..d745ec8b63 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -9,6 +9,7 @@ import java.util.NoSuchElementException; import java.util.PrimitiveIterator.OfInt; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createCantEscape; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createTheEndOfLineCanNotBeEscapedException; final class CucumberExpressionTokenizer { @@ -90,7 +91,7 @@ private void advanceTokenTypes() { private Token convertBufferToToken(Type currentTokenType) { int escapeTokens = 0; - if(currentTokenType == Type.TEXT){ + if (currentTokenType == Type.TEXT) { escapeTokens = escaped; escaped = 0; } @@ -100,32 +101,44 @@ private Token convertBufferToToken(Type currentTokenType) { this.index = endIndex; return t; } - }; - } + private Type tokenTypeOf(Integer token, boolean treatAsText) { + if (treatAsText) { + switch (token) { + case (int) '\\': + case (int) '/': + case (int) '{': + case (int) '}': + case (int) '(': + case (int) ')': + return Type.TEXT; + } + if (Character.isWhitespace(token)) { + return Type.TEXT; + } + throw createCantEscape(expression, index + escaped); + } + + if (Character.isWhitespace(token)) { + return Type.WHITE_SPACE; + } + + switch (token) { + case (int) '/': + return Type.ALTERNATION; + case (int) '{': + return Type.BEGIN_PARAMETER; + case (int) '}': + return Type.END_PARAMETER; + case (int) '(': + return Type.BEGIN_OPTIONAL; + case (int) ')': + return Type.END_OPTIONAL; + } + return Type.TEXT; + } + }; - private Type tokenTypeOf(Integer c, boolean treatAsText) { - if (treatAsText) { - return Type.TEXT; - } - - if (Character.isWhitespace(c)) { - return Type.WHITE_SPACE; - } - - switch (c) { - case (int) '/': - return Type.ALTERNATION; - case (int) '{': - return Type.BEGIN_PARAMETER; - case (int) '}': - return Type.END_PARAMETER; - case (int) '(': - return Type.BEGIN_OPTIONAL; - case (int) ')': - return Type.END_OPTIONAL; - } - return Type.TEXT; } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 575a9becaa..aff0f5afd9 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -90,12 +90,21 @@ void optionalPhrase() { @Test void escapedEndOfLine() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 2:\n" + "\n" + "\\\n" + " ^\n" + - "You can use '\\\\' to escape the the '\\'")); - fail(); + "The end of line can not be escaped. You can use '\\\\' to escape the the '\\'")); + } + + @Test + void escapeNonReservedCharacter() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\[")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 2:\n" + + "\n" + + "\\[\n" + + " ^\n" + + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped. If you did mean to use an '\\' you can use '\\\\' to escape it")); } @Test @@ -110,11 +119,11 @@ void escapedBackSlash() { @Test void openingBrace() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 1:\n" + "\n" + "{\n" + "^\n" + - "The '{' at 1 did not have a matching '}'. \n" + + "The '{' does not have a matching '}'. \n" + "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n" )); } @@ -131,22 +140,22 @@ void closingBrace() { @Test void unfinishedParameter() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 1:\n" + "\n" + "{string\n" + "^\n" + - "The '{' at 1 did not have a matching '}'. \n" + + "The '{' does not have a matching '}'. \n" + "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n")); } @Test void openingParenthesis() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 1:\n" + "\n" + "(\n" + "^\n" + - "The '(' at 1 did not have a matching ')'. \n" + + "The '(' does not have a matching ')'. \n" + "If you did not intended to use optional text you can use '\\(' to escape the optional text\n" )); } @@ -271,105 +280,105 @@ void emptyAlternations() { )); } - // @Test - // void escapedAlternation() { - // assertThat(astOf("mice\\/rats"), equalTo( - // new AstNode(EXPRESSION_NODE, - // new AstNode(TEXT_NODE, -1, -1, "mice/rats") - // ) - // )); - // } - // - // @Test - // void alternationPhrase() { - // assertThat(astOf("three hungry/blind mice"), equalTo( - // new AstNode(EXPRESSION_NODE, - // new AstNode(TEXT_NODE, -1, -1, "three"), - // new AstNode(TEXT_NODE, -1, -1, " "), - // new AstNode(ALTERNATION_NODE, - // new AstNode(ALTERNATIVE_NODE, - // new AstNode(TEXT_NODE, -1, -1, "hungry") - // ), - // new AstNode(ALTERNATIVE_NODE, - // new AstNode(TEXT_NODE, -1, -1, "blind") - // ) - // ), - // new AstNode(TEXT_NODE, -1, -1, " "), - // new AstNode(TEXT_NODE, -1, -1, "mice") - // ) - // )); - // } - // - // @Test - // void alternationWithWhiteSpace() { - // assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( - // new AstNode(EXPRESSION_NODE, - // new AstNode(ALTERNATION_NODE, - // new AstNode(ALTERNATIVE_NODE, - // new AstNode(TEXT_NODE, -1, -1, " three hungry") - // ), - // new AstNode(ALTERNATIVE_NODE, - // new AstNode(TEXT_NODE, -1, -1, "blind mice ") - // ) - // ) - // - // ) - // )); - // } - // - // @Test - // void alternationWithUnusedEndOptional() { - // assertThat(astOf("three )blind\\ mice/rats"), equalTo( - // new AstNode(EXPRESSION_NODE, - // new AstNode(TEXT_NODE, -1, -1, "three"), - // new AstNode(TEXT_NODE, -1, -1, " "), - // new AstNode(ALTERNATION_NODE, - // new AstNode(ALTERNATIVE_NODE, - // new AstNode(TEXT_NODE, -1, -1, ")"), - // new AstNode(TEXT_NODE, -1, -1, "blind mice") - // ), - // new AstNode(ALTERNATIVE_NODE, - // new AstNode(TEXT_NODE, -1, -1, "rats") - // ) - // ) - // ) - // )); - // } - // - // @Test - // void alternationWithUnusedStartOptional() { - // CucumberExpressionException exception = assertThrows( - // CucumberExpressionException.class, - // () -> astOf("three blind\\ mice/rats(")); - // assertThat(exception.getMessage(), is("This Cucumber Expression has problem:\n" + - // "\n" + - // "three blind mice/rats(\n" + - // " ^\n" + - // "The '(' at 22 did not have a matching ')'. \n" + - // "If you did not intended to use optional text you can use '\\(' to escape the optional text\n")); - // } - // - // @Test - // void alternationFollowedByOptional() { - // assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( - // new AstNode(EXPRESSION_NODE, - // new AstNode(TEXT_NODE, -1, -1, "three"), - // new AstNode(TEXT_NODE, -1, -1, " "), - // new AstNode(ALTERNATION_NODE, - // new AstNode(ALTERNATIVE_NODE, - // new AstNode(TEXT_NODE, -1, -1, "blind rat") - // ), - // new AstNode(ALTERNATIVE_NODE, - // new AstNode(TEXT_NODE, -1, -1, "cat"), - // new AstNode(OPTIONAL_NODE, - // new AstNode(TEXT_NODE, -1, -1, "s") - // ) - // ) - // ) - // ) - // )); - // } - // + @Test + void escapedAlternation() { + assertThat(astOf("mice\\/rats"), equalTo( + new AstNode(EXPRESSION_NODE, 0, 10, + new AstNode(TEXT_NODE, 0, 10, "mice/rats") + ) + )); + } + + @Test + void alternationPhrase() { + assertThat(astOf("three hungry/blind mice"), equalTo( + new AstNode(EXPRESSION_NODE, 0, 23, + new AstNode(TEXT_NODE, 0, 5, "three"), + new AstNode(TEXT_NODE, 5, 6, " "), + new AstNode(ALTERNATION_NODE, 6,18, + new AstNode(ALTERNATIVE_NODE, 6, 12, + new AstNode(TEXT_NODE, 6, 12, "hungry") + ), + new AstNode(ALTERNATIVE_NODE, 13, 18, + new AstNode(TEXT_NODE, 13, 18, "blind") + ) + ), + new AstNode(TEXT_NODE, 18, 19, " "), + new AstNode(TEXT_NODE, 19, 23, "mice") + ) + )); + } + + @Test + void alternationWithWhiteSpace() { + assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( + new AstNode(EXPRESSION_NODE, 0, 29, + new AstNode(ALTERNATION_NODE, 0,29, + new AstNode(ALTERNATIVE_NODE,0,15, + new AstNode(TEXT_NODE, 0, 15, " three hungry") + ), + new AstNode(ALTERNATIVE_NODE,16,29, + new AstNode(TEXT_NODE, 16, 29, "blind mice ") + ) + ) + + ) + )); + } + + @Test + void alternationWithUnusedEndOptional() { + assertThat(astOf("three )blind\\ mice/rats"), equalTo( + new AstNode(EXPRESSION_NODE,0,23, + new AstNode(TEXT_NODE, 0, 5, "three"), + new AstNode(TEXT_NODE, 5, 6, " "), + new AstNode(ALTERNATION_NODE,6,23, + new AstNode(ALTERNATIVE_NODE,6,18, + new AstNode(TEXT_NODE, 6, 7, ")"), + new AstNode(TEXT_NODE, 7, 18, "blind mice") + ), + new AstNode(ALTERNATIVE_NODE,19,23, + new AstNode(TEXT_NODE, 19, 23, "rats") + ) + ) + ) + )); + } + + @Test + void alternationWithUnusedStartOptional() { + CucumberExpressionException exception = assertThrows( + CucumberExpressionException.class, + () -> astOf("three blind\\ mice/rats(")); + assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 22:\n" + + "\n" + + "three blind mice/rats(\n" + + " ^\n" + + "The '(' does not have a matching ')'. \n" + + "If you did not intended to use optional text you can use '\\(' to escape the optional text\n")); + } + + @Test + void alternationFollowedByOptional() { + assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( + new AstNode(EXPRESSION_NODE,0,23, + new AstNode(TEXT_NODE, 0, 5, "three"), + new AstNode(TEXT_NODE, 5, 6, " "), + new AstNode(ALTERNATION_NODE,6,23, + new AstNode(ALTERNATIVE_NODE,6,16, + new AstNode(TEXT_NODE, 6, 16, "blind rat") + ), + new AstNode(ALTERNATIVE_NODE,17,23, + new AstNode(TEXT_NODE, 17, 20, "cat"), + new AstNode(OPTIONAL_NODE,20,23, + new AstNode(TEXT_NODE, 21, 22, "s") + ) + ) + ) + ) + )); + } + private AstNode astOf(String expression) { return parser.parse(expression); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 106383d85f..db408d342b 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -76,7 +76,11 @@ public void does_not_allow_empty_optional() { public void does_not_allow_alternation_with_empty_alternative() { Executable testMethod = () -> match("three brown//black mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("Alternative may not be empty: three brown//black mice"))); + assertThat(thrownException.getMessage(), is(equalTo("This Cucumber Expression has problem at column 13:\n" + + "\n" + + "three brown//black mice\n" + + " ^\n" + + "Alternative may not be empty. If you did not mean to use an alternative you can use '\\\\' to escape the the '\\'"))); } @Test diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionFactoryTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionFactoryTest.java index f83468fb71..7eec010bd9 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionFactoryTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionFactoryTest.java @@ -46,7 +46,7 @@ public void creates_cucumber_expression_for_escaped_parenthesis_with_alpha() { @Test public void creates_cucumber_expression_for_parenthesis_with_regex_symbols() { - assertCucumberExpression("the temperature is (\\+){int} degrees celsius"); + assertCucumberExpression("the temperature is (+){int} degrees celsius"); } @Test diff --git a/cucumber-expressions/javascript/examples.txt b/cucumber-expressions/javascript/examples.txt index 35b7bd17a9..c6acb6958e 100644 --- a/cucumber-expressions/javascript/examples.txt +++ b/cucumber-expressions/javascript/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \[]^$.|?*+ +I have {int} cuke(s) and some \\[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- diff --git a/cucumber-expressions/ruby/examples.txt b/cucumber-expressions/ruby/examples.txt index 35b7bd17a9..c6acb6958e 100644 --- a/cucumber-expressions/ruby/examples.txt +++ b/cucumber-expressions/ruby/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \[]^$.|?*+ +I have {int} cuke(s) and some \\[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- From ca188be5a5646e9dd941447960cae9d4c4468261 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 16 Jul 2020 13:45:48 +0200 Subject: [PATCH 076/183] WIP --- .../CucumberExpression.java | 20 +-- .../CucumberExpressionException.java | 114 +++++++++++++----- .../CucumberExpressionParserTest.java | 29 +++-- .../CucumberExpressionTest.java | 66 +++++++--- 4 files changed, 158 insertions(+), 71 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 9053ec8788..29cde9b1c6 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -6,18 +6,20 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.regex.Pattern; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeIsEmpty; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedHere; import static java.util.stream.Collectors.joining; @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { private static final Pattern ESCAPE_PATTERN = Pattern.compile("([\\\\^\\[({$.|?*+})\\]])"); - private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional: "; - private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative: "; + private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional"; + private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative"; private static final String OPTIONAL_MAY_NOT_BE_EMPTY = "Optional may not be empty: "; private static final String ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS = "Alternative may not exclusively contain optionals: "; @@ -25,14 +27,13 @@ public final class CucumberExpression implements Expression { private final String source; private final TreeRegexp treeRegexp; private final ParameterTypeRegistry parameterTypeRegistry; - private final AstNode ast; CucumberExpression(String expression, ParameterTypeRegistry parameterTypeRegistry) { this.source = expression; this.parameterTypeRegistry = parameterTypeRegistry; CucumberExpressionParser parser = new CucumberExpressionParser(); - this.ast = parser.parse(expression); + AstNode ast = parser.parse(expression); String pattern = rewriteToRegex(ast); treeRegexp = new TreeRegexp(pattern); } @@ -112,6 +113,7 @@ private String rewriteExpression(AstNode node) { } private void assertNotEmpty(AstNode node, String message) { + //TODO: Change message boolean hasTextNode = node.nodes() .stream() .map(AstNode::type) @@ -122,11 +124,11 @@ private void assertNotEmpty(AstNode node, String message) { } private void assertNoParameters(AstNode node, String message) { - boolean hasParameter = node.nodes().stream() - .map(AstNode::type) - .anyMatch(type -> type == PARAMETER_NODE); - if (hasParameter) { - throw new CucumberExpressionException(message + source); + Optional hasParameter = node.nodes().stream() + .filter(astNode -> PARAMETER_NODE.equals(astNode.type())) + .findFirst(); + if (hasParameter.isPresent()) { + throw createParameterIsNotAllowedHere(hasParameter.get(), this.source, message); } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 113c4552af..84da2f0972 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -52,19 +52,13 @@ private static List getGroupValues(List argGroups) { static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, List tokens, int current) { - int index = currentTokenIndex(tokens, current); - return new CucumberExpressionException("" + - thisCucumberExpressionHasAProblemAt(index) + - "\n" + - expressionOf(tokens) + "\n" + - pointAt(index) + "\n" + - "The '" + beginToken.symbol() + "' does not have a matching '" + endToken.symbol() + "'. " + "\n" + - "If you did not intended to use " + beginToken.purpose() + " you can use '\\" + beginToken - .symbol() + "' to escape the " + beginToken.purpose() + "\n"); - } - - private static String thisPRo(int i) { - return "This Cucumber Expression has problem at index '" + i + "' :" + "\n"; + return new CucumberExpressionException(message( + currentTokenIndex(tokens, current), + expressionOf(tokens), + pointAt(tokens.get(current)), + "The '" + beginToken.symbol() + "' does not have a matching '" + endToken.symbol() + "'", + "If you did not intend to use " + beginToken.purpose() + " you can use '\\" + beginToken + .symbol() + "' to escape the " + beginToken.purpose())); } private static String expressionOf(List expression) { @@ -76,11 +70,56 @@ private static int currentTokenIndex(List expression, int current) { } static CucumberExpressionException createTheEndOfLineCanNotBeEscapedException(String expression) { - return new CucumberExpressionException(thisCucumberExpressionHasAProblemAt(expression.length()) + + int index = expression.length(); + return new CucumberExpressionException(message( + index, + expression, + pointAt(index), + "The end of line can not be escaped", + "You can use '\\\\' to escape the the '\\'" + )); + } + + static CucumberExpressionException createAlternativeIsEmpty(String expression, AstNode node) { + return new CucumberExpressionException(message( + node.start(), + expression, + pointAt(node), + "Alternative may not be empty", + "If you did not mean to use an alternative you can use '\\/' to escape the the '/'")); + } + + public static CucumberExpressionException createParameterIsNotAllowedHere(AstNode node, String expression, + String message) { + return new CucumberExpressionException(message( + node.start(), + expression, + pointAt(node), + message, + "If you did not mean to use an alternative you can use '\\{' to escape the the '{'")); + } + + private static String thisCucumberExpressionHasAProblemAt(int index) { + return "This Cucumber Expression has problem at column " + (index + 1) + ":" + "\n"; + } + + static CucumberExpressionException createCantEscape(String expression, int index) { + return new CucumberExpressionException(message( + index, + expression, + pointAt(index), + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped", + "If you did mean to use an '\\' you can use '\\\\' to escape it")); + } + + private static String message(int index, String expression, StringBuilder pointer, String problem, + String solution) { + return thisCucumberExpressionHasAProblemAt(index) + "\n" + expression + "\n" + - pointAt(expression.length()) + "\n" + - "The end of line can not be escaped. You can use '\\\\' to escape the the '\\'"); + pointer + "\n" + + problem + ".\n" + + solution; } private static StringBuilder pointAt(int index) { @@ -92,24 +131,35 @@ private static StringBuilder pointAt(int index) { return pointer; } - static CucumberExpressionException createAlternativeIsEmpty(String expression, AstNode node) { - return new CucumberExpressionException(thisCucumberExpressionHasAProblemAt(node.start()) + - "\n" + - expression + "\n" + - pointAt(node.start()) + "\n" + - "Alternative may not be empty. If you did not mean to use an alternative you can use '\\\\' to escape the the '\\'"); - } - - private static String thisCucumberExpressionHasAProblemAt(int index) { - return "This Cucumber Expression has problem at column " + (index+1) + ":" + "\n"; + //TODO: Dedupe + private static StringBuilder pointAt(AstNode node) { + StringBuilder pointer = new StringBuilder(); + for (int i = 0; i < node.start(); i++) { + pointer.append(" "); + } + pointer.append("^"); + if (node.start() + 1 < node.end()) { + for (int i = node.start() + 1; i < node.end() - 1; i++) { + pointer.append("-"); + } + pointer.append("^"); + } + return pointer; } - static CucumberExpressionException createCantEscape(String expression, int index) { - return new CucumberExpressionException(thisCucumberExpressionHasAProblemAt(index) + - "\n" + - expression + "\n" + - pointAt(index) + "\n" + - "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped. If you did mean to use an '\\' you can use '\\\\' to escape it"); + private static StringBuilder pointAt(Token token) { + StringBuilder pointer = new StringBuilder(); + for (int i = 0; i < token.start(); i++) { + pointer.append(" "); + } + pointer.append("^"); + if (token.start() + 1 < token.end()) { + for (int i = token.start() + 1; i < token.end() - 1; i++) { + pointer.append("-"); + } + pointer.append("^"); + } + return pointer; } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index aff0f5afd9..991790d080 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -94,7 +94,8 @@ void escapedEndOfLine() { "\n" + "\\\n" + " ^\n" + - "The end of line can not be escaped. You can use '\\\\' to escape the the '\\'")); + "The end of line can not be escaped.\n" + + "You can use '\\\\' to escape the the '\\'")); } @Test @@ -104,7 +105,8 @@ void escapeNonReservedCharacter() { "\n" + "\\[\n" + " ^\n" + - "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped. If you did mean to use an '\\' you can use '\\\\' to escape it")); + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped.\n" + + "If you did mean to use an '\\' you can use '\\\\' to escape it")); } @Test @@ -123,8 +125,8 @@ void openingBrace() { "\n" + "{\n" + "^\n" + - "The '{' does not have a matching '}'. \n" + - "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n" + "The '{' does not have a matching '}'.\n" + + "If you did not intend to use a parameter you can use '\\{' to escape the a parameter" )); } @@ -144,8 +146,8 @@ void unfinishedParameter() { "\n" + "{string\n" + "^\n" + - "The '{' does not have a matching '}'. \n" + - "If you did not intended to use a parameter you can use '\\{' to escape the a parameter\n")); + "The '{' does not have a matching '}'.\n" + + "If you did not intend to use a parameter you can use '\\{' to escape the a parameter")); } @Test @@ -155,8 +157,8 @@ void openingParenthesis() { "\n" + "(\n" + "^\n" + - "The '(' does not have a matching ')'. \n" + - "If you did not intended to use optional text you can use '\\(' to escape the optional text\n" + "The '(' does not have a matching ')'.\n" + + "If you did not intend to use optional text you can use '\\(' to escape the optional text" )); } @@ -350,12 +352,13 @@ void alternationWithUnusedStartOptional() { CucumberExpressionException exception = assertThrows( CucumberExpressionException.class, () -> astOf("three blind\\ mice/rats(")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 22:\n" + + assertThat(exception.getMessage(), is("" + + "This Cucumber Expression has problem at column 22:\n" + "\n" + - "three blind mice/rats(\n" + - " ^\n" + - "The '(' does not have a matching ')'. \n" + - "If you did not intended to use optional text you can use '\\(' to escape the optional text\n")); + "three blind\\ mice/rats(\n" + + " ^\n" + + "The '(' does not have a matching ')'.\n" + + "If you did not intend to use optional text you can use '\\(' to escape the optional text")); } @Test diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index db408d342b..a3a1d04349 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -80,14 +80,16 @@ public void does_not_allow_alternation_with_empty_alternative() { "\n" + "three brown//black mice\n" + " ^\n" + - "Alternative may not be empty. If you did not mean to use an alternative you can use '\\\\' to escape the the '\\'"))); + "Alternative may not be empty." + + "\nIf you did not mean to use an alternative you can use '\\/' to escape the the '/'"))); } @Test public void allows_optional_adjacent_to_alternation() { Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("Alternative may not exclusively contain optionals: three (brown)/black mice"))); + assertThat(thrownException.getMessage(), + is(equalTo("Alternative may not exclusively contain optionals: three (brown)/black mice"))); } @Test @@ -97,7 +99,8 @@ public void matches_double_quoted_string() { @Test public void matches_multiple_double_quoted_strings() { - assertEquals(asList("blind", "crippled"), match("three {string} and {string} mice", "three \"blind\" and \"crippled\" mice")); + assertEquals(asList("blind", "crippled"), + match("three {string} and {string} mice", "three \"blind\" and \"crippled\" mice")); } @Test @@ -107,7 +110,8 @@ public void matches_single_quoted_string() { @Test public void matches_multiple_single_quoted_strings() { - assertEquals(asList("blind", "crippled"), match("three {string} and {string} mice", "three 'blind' and 'crippled' mice")); + assertEquals(asList("blind", "crippled"), + match("three {string} and {string} mice", "three 'blind' and 'crippled' mice")); } @Test @@ -152,21 +156,28 @@ public void matches_single_quoted_empty_string_as_empty_string_along_with_other_ @Test public void matches_double_quoted_empty_string_as_empty_string_along_with_other_strings() { - assertEquals(asList("", "handsome"), match("three {string} and {string} mice", "three \"\" and \"handsome\" mice")); + assertEquals(asList("", "handsome"), + match("three {string} and {string} mice", "three \"\" and \"handsome\" mice")); } @Test public void matches_escaped_parenthesis() { - assertEquals(emptyList(), match("three \\(exceptionally) \\{string} mice", "three (exceptionally) {string} mice")); - assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); - assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); - parameterTypeRegistry.defineParameterType(new ParameterType<>("{string}", "\"(.*)\"", String.class, (String arg) -> arg)); - assertEquals(singletonList("blind"), match("three ((exceptionally\\)) {{string\\}} mice", "three (exceptionally) \"blind\" mice")); + assertEquals(emptyList(), + match("three \\(exceptionally) \\{string} mice", "three (exceptionally) {string} mice")); + assertEquals(singletonList("blind"), + match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); + assertEquals(singletonList("blind"), + match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); + parameterTypeRegistry + .defineParameterType(new ParameterType<>("{string}", "\"(.*)\"", String.class, (String arg) -> arg)); + assertEquals(singletonList("blind"), + match("three ((exceptionally\\)) {{string\\}} mice", "three (exceptionally) \"blind\" mice")); } @Test public void matches_doubly_escaped_parenthesis() { - assertEquals(singletonList("blind"), match("three \\\\(exceptionally) \\\\{string} mice", "three \\exceptionally \\\"blind\" mice")); + assertEquals(singletonList("blind"), + match("three \\\\(exceptionally) \\\\{string} mice", "three \\exceptionally \\\"blind\" mice")); } @Test @@ -212,7 +223,8 @@ public void does_not_allow_parameter_type_with_left_bracket() { final Executable testMethod = () -> match("{[string]}", "something"); final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Illegal character '[' in parameter name {[string]}."))); + assertThat("Unexpected message", thrownException.getMessage(), + is(equalTo("Illegal character '[' in parameter name {[string]}."))); } @Test @@ -220,8 +232,10 @@ public void throws_unknown_parameter_type() { final Executable testMethod = () -> match("{unknown}", "something"); - final UndefinedParameterTypeException thrownException = assertThrows(UndefinedParameterTypeException.class, testMethod); - assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Undefined parameter type {unknown}. Please register a ParameterType for {unknown}."))); + final UndefinedParameterTypeException thrownException = assertThrows(UndefinedParameterTypeException.class, + testMethod); + assertThat(thrownException.getMessage(), + is(equalTo("Undefined parameter type {unknown}. Please register a ParameterType for {unknown}."))); } @Test @@ -230,7 +244,13 @@ public void does_not_allow_optional_parameter_types() { final Executable testMethod = () -> match("({int})", "3"); final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Parameter types cannot be optional: ({int})"))); + assertThat(thrownException.getMessage(), is(equalTo("" + + "This Cucumber Expression has problem at column 2:\n" + + "\n" + + "({int})\n" + + " ^---^\n" + + "Parameter types cannot be optional.\n" + + "If you did not mean to use an alternative you can use '\\(' to escape the the '('"))); } @Test @@ -244,7 +264,13 @@ public void does_not_allow_text_parameter_type_alternation() { final Executable testMethod = () -> match("x/{int}", "3"); final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Parameter types cannot be alternative: x/{int}"))); + assertThat(thrownException.getMessage(), is(equalTo("" + + "This Cucumber Expression has problem at column 3:\n" + + "\n" + + "x/{int}\n" + + " ^---^\n" + + "Parameter types cannot be alternative.\n" + + "If you did not mean to use an alternative you can use '\\{' to escape the the '{'"))); } @Test @@ -253,7 +279,13 @@ public void does_not_allow_parameter_type_text_alternation() { final Executable testMethod = () -> match("{int}/x", "3"); final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Parameter types cannot be alternative: {int}/x"))); + assertThat(thrownException.getMessage(), is(equalTo("" + + "This Cucumber Expression has problem at column 1:\n" + + "\n" + + "{int}/x\n" + + "^---^\n" + + "Parameter types cannot be alternative.\n" + + "If you did not mean to use an alternative you can use '\\{' to escape the the '{'"))); } @Test From 98583f5ed31cd63c6d8ec3dbba0465e44b3dc73c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 16 Jul 2020 14:27:28 +0200 Subject: [PATCH 077/183] WIP --- .../CucumberExpressionException.java | 2 +- .../CucumberExpressionParserTest.java | 12 ++++++------ .../cucumberexpressions/CucumberExpressionTest.java | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 84da2f0972..da91d1b968 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -100,7 +100,7 @@ public static CucumberExpressionException createParameterIsNotAllowedHere(AstNod } private static String thisCucumberExpressionHasAProblemAt(int index) { - return "This Cucumber Expression has problem at column " + (index + 1) + ":" + "\n"; + return "This Cucumber Expression has a problem at column " + (index + 1) + ":" + "\n"; } static CucumberExpressionException createCantEscape(String expression, int index) { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 991790d080..5e1f06f0c1 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -90,7 +90,7 @@ void optionalPhrase() { @Test void escapedEndOfLine() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 2:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + "\n" + "\\\n" + " ^\n" + @@ -101,7 +101,7 @@ void escapedEndOfLine() { @Test void escapeNonReservedCharacter() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\[")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 2:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + "\n" + "\\[\n" + " ^\n" + @@ -121,7 +121,7 @@ void escapedBackSlash() { @Test void openingBrace() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 1:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + "\n" + "{\n" + "^\n" + @@ -142,7 +142,7 @@ void closingBrace() { @Test void unfinishedParameter() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 1:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + "\n" + "{string\n" + "^\n" + @@ -153,7 +153,7 @@ void unfinishedParameter() { @Test void openingParenthesis() { CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); - assertThat(exception.getMessage(), is("This Cucumber Expression has problem at column 1:\n" + + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + "\n" + "(\n" + "^\n" + @@ -353,7 +353,7 @@ void alternationWithUnusedStartOptional() { CucumberExpressionException.class, () -> astOf("three blind\\ mice/rats(")); assertThat(exception.getMessage(), is("" + - "This Cucumber Expression has problem at column 22:\n" + + "This Cucumber Expression has a problem at column 22:\n" + "\n" + "three blind\\ mice/rats(\n" + " ^\n" + diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index a3a1d04349..9431eb1795 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -76,7 +76,7 @@ public void does_not_allow_empty_optional() { public void does_not_allow_alternation_with_empty_alternative() { Executable testMethod = () -> match("three brown//black mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("This Cucumber Expression has problem at column 13:\n" + + assertThat(thrownException.getMessage(), is(equalTo("This Cucumber Expression has a problem at column 13:\n" + "\n" + "three brown//black mice\n" + " ^\n" + @@ -245,7 +245,7 @@ public void does_not_allow_optional_parameter_types() { final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has problem at column 2:\n" + + "This Cucumber Expression has a problem at column 2:\n" + "\n" + "({int})\n" + " ^---^\n" + @@ -265,7 +265,7 @@ public void does_not_allow_text_parameter_type_alternation() { final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has problem at column 3:\n" + + "This Cucumber Expression has a problem at column 3:\n" + "\n" + "x/{int}\n" + " ^---^\n" + @@ -280,7 +280,7 @@ public void does_not_allow_parameter_type_text_alternation() { final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has problem at column 1:\n" + + "This Cucumber Expression has a problem at column 1:\n" + "\n" + "{int}/x\n" + "^---^\n" + From acd344e32535d999dcd1f808187703a39b7e27a9 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 17 Jul 2020 15:54:22 +0200 Subject: [PATCH 078/183] Fix off by one --- .../io/cucumber/cucumberexpressions/Ast.java | 17 ++-- .../CucumberExpression.java | 51 ++++++----- .../CucumberExpressionException.java | 91 +++++++++---------- .../CucumberExpressionParser.java | 89 +++++++++--------- .../CucumberExpressionParserTest.java | 2 +- .../CucumberExpressionTest.java | 28 ++++-- 6 files changed, 151 insertions(+), 127 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index aa6c40f31e..83df65b490 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -10,7 +10,12 @@ final class Ast { - static final class AstNode { + interface Located { + int start(); + int end(); + } + + static final class AstNode implements Located { private final Type type; private final List nodes; @@ -48,10 +53,10 @@ enum Type { EXPRESSION_NODE } - int start(){ + public int start(){ return startIndex; } - int end(){ + public int end(){ return endIndex; } @@ -128,7 +133,7 @@ public int hashCode() { } - static final class Token { + static final class Token implements Located { final int startIndex; final int endIndex; @@ -142,10 +147,10 @@ static final class Token { this.endIndex = endIndex; } - int start(){ + public int start(){ return startIndex; } - int end(){ + public int end(){ return endIndex; } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 29cde9b1c6..f6d82d2a2a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -6,22 +6,21 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import java.util.function.Function; import java.util.regex.Pattern; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeIsEmpty; -import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedHere; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayExclusivelyContainOptionals; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInAlternative; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInOptional; import static java.util.stream.Collectors.joining; @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { private static final Pattern ESCAPE_PATTERN = Pattern.compile("([\\\\^\\[({$.|?*+})\\]])"); - private static final String PARAMETER_TYPES_CANNOT_BE_OPTIONAL = "Parameter types cannot be optional"; - private static final String PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = "Parameter types cannot be alternative"; - private static final String OPTIONAL_MAY_NOT_BE_EMPTY = "Optional may not be empty: "; - private static final String ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS = "Alternative may not exclusively contain optionals: "; private final List> parameterTypes = new ArrayList<>(); private final String source; @@ -62,8 +61,8 @@ private static String escapeRegex(String text) { } private String rewriteOptional(AstNode node) { - assertNoParameters(node, PARAMETER_TYPES_CANNOT_BE_OPTIONAL); - assertNotEmpty(node, OPTIONAL_MAY_NOT_BE_EMPTY); + assertNoParameters(node, astNode -> createParameterIsNotAllowedInOptional(astNode, source)); + assertNotEmpty(node, astNode -> createOptionalMayNotBeEmpty(astNode, source)); return node.nodes().stream() .map(this::rewriteToRegex) .collect(joining("", "(?:", ")?")); @@ -73,10 +72,10 @@ private String rewriteAlternation(AstNode node) { // Make sure the alternative parts aren't empty and don't contain parameter types for (AstNode alternative : node.nodes()) { if (alternative.nodes().isEmpty()) { - throw createAlternativeIsEmpty(this.source, alternative); + throw createAlternativeIsEmpty(alternative, source); } - assertNoParameters(alternative, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE); - assertNotEmpty(alternative, ALTERNATIVE_MAY_NOT_EXCLUSIVELY_CONTAIN_OPTIONALS); + assertNoParameters(alternative, astNode -> createParameterIsNotAllowedInAlternative(astNode, source)); + assertNotEmpty(alternative, astNode -> createAlternativeMayExclusivelyContainOptionals(astNode, source)); } return node.nodes() .stream() @@ -112,24 +111,25 @@ private String rewriteExpression(AstNode node) { .collect(joining("", "^", "$")); } - private void assertNotEmpty(AstNode node, String message) { - //TODO: Change message - boolean hasTextNode = node.nodes() + private void assertNotEmpty(AstNode node, + Function createNodeWasNotEmptyException) { + node.nodes() .stream() - .map(AstNode::type) - .anyMatch(type -> type == TEXT_NODE); - if (!hasTextNode) { - throw new CucumberExpressionException(message + source); - } + .filter(astNode -> TEXT_NODE.equals(astNode.type())) + .findFirst() + .orElseThrow(() -> createNodeWasNotEmptyException.apply(node)); } - private void assertNoParameters(AstNode node, String message) { - Optional hasParameter = node.nodes().stream() + private void assertNoParameters(AstNode node, + Function createNodeContainedAParameterException) { + node.nodes() + .stream() .filter(astNode -> PARAMETER_NODE.equals(astNode.type())) - .findFirst(); - if (hasParameter.isPresent()) { - throw createParameterIsNotAllowedHere(hasParameter.get(), this.source, message); - } + .map(createNodeContainedAParameterException) + .findFirst() + .ifPresent(nodeContainedAParameterException -> { + throw nodeContainedAParameterException; + }); } @Override @@ -161,4 +161,5 @@ public String getSource() { public Pattern getRegexp() { return treeRegexp.pattern(); } + } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index da91d1b968..f5b93d745b 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -1,13 +1,13 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.Ast.AstNode; +import io.cucumber.cucumberexpressions.Ast.Located; import io.cucumber.cucumberexpressions.Ast.Token; import io.cucumber.cucumberexpressions.Ast.Token.Type; import org.apiguardian.api.API; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; @API(status = API.Status.STABLE) public class CucumberExpressionException extends RuntimeException { @@ -50,37 +50,28 @@ private static List getGroupValues(List argGroups) { return list; } - static CucumberExpressionException createMissingEndTokenException(Type beginToken, Type endToken, - List tokens, int current) { + static CucumberExpressionException createMissingEndTokenException(String expression, Type beginToken, Type endToken, + Token current) { return new CucumberExpressionException(message( - currentTokenIndex(tokens, current), - expressionOf(tokens), - pointAt(tokens.get(current)), + current.start(), + expression, + pointAt(current), "The '" + beginToken.symbol() + "' does not have a matching '" + endToken.symbol() + "'", "If you did not intend to use " + beginToken.purpose() + " you can use '\\" + beginToken .symbol() + "' to escape the " + beginToken.purpose())); } - private static String expressionOf(List expression) { - return expression.stream().map(token -> token.text).collect(Collectors.joining()); - } - - private static int currentTokenIndex(List expression, int current) { - return expression.stream().limit(current).mapToInt(value -> value.text.length()).sum(); - } - static CucumberExpressionException createTheEndOfLineCanNotBeEscapedException(String expression) { - int index = expression.length(); return new CucumberExpressionException(message( - index, + expression.length(), expression, - pointAt(index), + pointAt(expression.length()), "The end of line can not be escaped", "You can use '\\\\' to escape the the '\\'" )); } - static CucumberExpressionException createAlternativeIsEmpty(String expression, AstNode node) { + static CucumberExpressionException createAlternativeIsEmpty(AstNode node, String expression) { return new CucumberExpressionException(message( node.start(), expression, @@ -89,14 +80,41 @@ static CucumberExpressionException createAlternativeIsEmpty(String expression, A "If you did not mean to use an alternative you can use '\\/' to escape the the '/'")); } - public static CucumberExpressionException createParameterIsNotAllowedHere(AstNode node, String expression, - String message) { + static CucumberExpressionException createParameterIsNotAllowedInAlternative(AstNode node, String expression) { return new CucumberExpressionException(message( node.start(), expression, pointAt(node), - message, - "If you did not mean to use an alternative you can use '\\{' to escape the the '{'")); + "An alternative may not contain a parameter type", + "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'")); + } + + static CucumberExpressionException createParameterIsNotAllowedInOptional(AstNode node, String expression) { + return new CucumberExpressionException(message( + node.start(), + expression, + pointAt(node), + "An optional may not contain a parameter type", + "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'")); + } + + static CucumberExpressionException createOptionalMayNotBeEmpty(AstNode node, String expression) { + return new CucumberExpressionException(message( + node.start(), + expression, + pointAt(node), + "An optional must contain some text", + "If you did not mean to use an optional you can use '\\(' to escape the the '('")); + } + + static CucumberExpressionException createAlternativeMayExclusivelyContainOptionals(AstNode node, + String expression) { + return new CucumberExpressionException(message( + node.start(), + expression, + pointAt(node), + "An alternative may not exclusively contain optionals", + "If you did not mean to use an optional you can use '\\(' to escape the the '('")); } private static String thisCucumberExpressionHasAProblemAt(int index) { @@ -122,22 +140,8 @@ private static String message(int index, String expression, StringBuilder pointe solution; } - private static StringBuilder pointAt(int index) { - StringBuilder pointer = new StringBuilder(); - for (int i = 0; i < index; i++) { - pointer.append(" "); - } - pointer.append("^"); - return pointer; - } - - //TODO: Dedupe - private static StringBuilder pointAt(AstNode node) { - StringBuilder pointer = new StringBuilder(); - for (int i = 0; i < node.start(); i++) { - pointer.append(" "); - } - pointer.append("^"); + private static StringBuilder pointAt(Located node) { + StringBuilder pointer = pointAt(node.start()); if (node.start() + 1 < node.end()) { for (int i = node.start() + 1; i < node.end() - 1; i++) { pointer.append("-"); @@ -147,19 +151,14 @@ private static StringBuilder pointAt(AstNode node) { return pointer; } - private static StringBuilder pointAt(Token token) { + private static StringBuilder pointAt(int index) { StringBuilder pointer = new StringBuilder(); - for (int i = 0; i < token.start(); i++) { + for (int i = 0; i < index; i++) { pointer.append(" "); } pointer.append("^"); - if (token.start() + 1 < token.end()) { - for (int i = token.start() + 1; i < token.end() - 1; i++) { - pointer.append("-"); - } - pointer.append("^"); - } return pointer; } + } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index db2533a22c..649cb27aea 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -31,8 +31,8 @@ final class CucumberExpressionParser { /* * text := token */ - private static final Parser textParser = (expression, current) -> { - Token token = expression.get(current); + private static final Parser textParser = (expression, tokens, current) -> { + Token token = tokens.get(current); return new Result(1, new AstNode(TEXT_NODE, token.start(), token.end(), token.text)); }; @@ -58,11 +58,11 @@ final class CucumberExpressionParser { ); // alternation := alternative* + ( '/' + alternative* )+ - private static final Parser alternativeSeparator = (expression, current) -> { - if (!lookingAt(expression, current, ALTERNATION)) { + private static final Parser alternativeSeparator = (expression, tokens, current) -> { + if (!lookingAt(tokens, current, ALTERNATION)) { return new Result(0); } - Token token = expression.get(current); + Token token = tokens.get(current); return new Result(1, new AstNode(ALTERNATIVE_NODE, token.start(), token.end(), "/")); }; @@ -78,21 +78,22 @@ final class CucumberExpressionParser { * boundary := whitespace | ^ | $ * alternative: = optional | parameter | text */ - private static final Parser alternationParser = (expression, current) -> { + private static final Parser alternationParser = (expression, tokens, current) -> { int previous = current - 1; - if (!lookingAt(expression, previous, START_OF_LINE, WHITE_SPACE)) { + if (!lookingAt(tokens, previous, START_OF_LINE, WHITE_SPACE)) { return new Result(0); } - Result result = parseTokensUntil(alternativeParsers, expression, current, WHITE_SPACE, END_OF_LINE); + Result result = parseTokensUntil(expression, alternativeParsers, tokens, current, WHITE_SPACE, END_OF_LINE); int subCurrent = current + result.consumed; if (result.ast.stream().noneMatch(astNode -> astNode.type() == ALTERNATIVE_NODE)) { return new Result(0); } - int start = expression.get(current).start(); - int end = expression.get(subCurrent).start(); + int start = tokens.get(current).start(); + int end = tokens.get(subCurrent).start(); // Does not consume right hand boundary token - return new Result(result.consumed, new AstNode(ALTERNATION_NODE, start, end, splitAlternatives(start, end, result.ast))); + return new Result(result.consumed, + new AstNode(ALTERNATION_NODE, start, end, splitAlternatives(start, end, result.ast))); }; /* @@ -113,12 +114,13 @@ final class CucumberExpressionParser { AstNode parse(String expression) { CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); List tokens = tokenizer.tokenize(expression); - Result result = cucumberExpressionParser.parse(tokens, 0); + Result result = cucumberExpressionParser.parse(expression, tokens, 0); return result.ast.get(0); } private interface Parser { - Result parse(List expression, int current); + Result parse(String expression, List tokens, int current); + } private static final class Result { @@ -128,10 +130,12 @@ private static final class Result { private Result(int consumed, AstNode... ast) { this(consumed, Arrays.asList(ast)); } + private Result(int consumed, List ast) { this.consumed = consumed; this.ast = ast; } + } private static Parser parseBetween( @@ -139,42 +143,44 @@ private static Parser parseBetween( Type beginToken, Type endToken, List parsers) { - return (expression, current) -> { - if (!lookingAt(expression, current, beginToken)) { + return (expression, tokens, current) -> { + if (!lookingAt(tokens, current, beginToken)) { return new Result(0); } int subCurrent = current + 1; - Result result = parseTokensUntil(parsers, expression, subCurrent, endToken); + Result result = parseTokensUntil(expression, parsers, tokens, subCurrent, endToken); subCurrent += result.consumed; // endToken not found - if (!lookingAt(expression, subCurrent, endToken)) { - throw createMissingEndTokenException(beginToken, endToken, expression, current); + if (!lookingAt(tokens, subCurrent, endToken)) { + throw createMissingEndTokenException(expression, beginToken, endToken, tokens.get(current)); } // consumes endToken - int start = expression.get(current).start(); - int end = expression.get(subCurrent).end(); + int start = tokens.get(current).start(); + int end = tokens.get(subCurrent).end(); return new Result(subCurrent + 1 - current, new AstNode(type, start, end, result.ast)); }; } - private static Result parseTokensUntil(List parsers, - List expression, - int startAt, - Type... endTokens) { + private static Result parseTokensUntil( + String expression, + List parsers, + List tokens, + int startAt, + Type... endTokens) { int current = startAt; - int size = expression.size(); + int size = tokens.size(); List ast = new ArrayList<>(); while (current < size) { - if (lookingAt(expression, current, endTokens)) { + if (lookingAt(tokens, current, endTokens)) { break; } - Result result = parseToken(parsers, expression, current); + Result result = parseToken(expression, parsers, tokens, current); if (result.consumed == 0) { // If configured correctly this will never happen // Keep to avoid infinite loops - throw new IllegalStateException("No eligible parsers for " + expression); + throw new IllegalStateException("No eligible parsers for " + tokens); } current += result.consumed; ast.addAll(result.ast); @@ -182,36 +188,36 @@ private static Result parseTokensUntil(List parsers, return new Result(current - startAt, ast); } - private static Result parseToken(List parsers, - List expression, - int startAt) { + private static Result parseToken(String expression, List parsers, + List tokens, + int startAt) { for (Parser parser : parsers) { - Result result = parser.parse(expression, startAt); + Result result = parser.parse(expression, tokens, startAt); if (result.consumed != 0) { return result; } } // If configured correctly this will never happen - throw new IllegalStateException("No eligible parsers for " + expression); + throw new IllegalStateException("No eligible parsers for " + tokens); } - private static boolean lookingAt(List expression, int at, Type... tokens) { - for (Type token : tokens) { - if (lookingAt(expression, at, token)) { + private static boolean lookingAt(List tokens, int at, Type... tokenTypes) { + for (Type tokeType : tokenTypes) { + if (lookingAt(tokens, at, tokeType)) { return true; } } return false; } - private static boolean lookingAt(List expression, int at, Type token) { + private static boolean lookingAt(List tokens, int at, Type token) { if (at < 0) { return token == START_OF_LINE; } - if (at >= expression.size()) { + if (at >= tokens.size()) { return token == END_OF_LINE; } - return expression.get(at).type == token; + return tokens.get(at).type == token; } private static List splitAlternatives(int start, int end, List astNode) { @@ -235,10 +241,11 @@ private static List splitAlternatives(int start, int end, List List astNodes = alternatives.get(i); if (i == 0) { alts.add(new AstNode(ALTERNATIVE_NODE, start, seperators.get(i).start(), astNodes)); - } else if( i == alternatives.size() - 1){ + } else if (i == alternatives.size() - 1) { alts.add(new AstNode(ALTERNATIVE_NODE, seperators.get(i - 1).end(), end, astNodes)); } else { - alts.add(new AstNode(ALTERNATIVE_NODE, seperators.get(i-1).end(), seperators.get(i).start(), astNodes)); + alts.add(new AstNode(ALTERNATIVE_NODE, seperators.get(i - 1).end(), seperators.get(i).start(), + astNodes)); } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 5e1f06f0c1..f300dfb1dd 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -353,7 +353,7 @@ void alternationWithUnusedStartOptional() { CucumberExpressionException.class, () -> astOf("three blind\\ mice/rats(")); assertThat(exception.getMessage(), is("" + - "This Cucumber Expression has a problem at column 22:\n" + + "This Cucumber Expression has a problem at column 23:\n" + "\n" + "three blind\\ mice/rats(\n" + " ^\n" + diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 9431eb1795..518044ced7 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -69,7 +69,13 @@ public void matches_alternation_in_optional_as_text() { public void does_not_allow_empty_optional() { Executable testMethod = () -> match("three () mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("Optional may not be empty: three () mice"))); + assertThat(thrownException.getMessage(), is(equalTo("" + + "This Cucumber Expression has a problem at column 7:\n" + + "\n" + + "three () mice\n" + + " ^^\n" + + "An optional must contain some text.\n" + + "If you did not mean to use an optional you can use '\\(' to escape the the '('"))); } @Test @@ -89,7 +95,13 @@ public void allows_optional_adjacent_to_alternation() { Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); assertThat(thrownException.getMessage(), - is(equalTo("Alternative may not exclusively contain optionals: three (brown)/black mice"))); + is(equalTo("" + + "This Cucumber Expression has a problem at column 7:\n" + + "\n" + + "three (brown)/black mice\n" + + " ^-----^\n" + + "An alternative may not exclusively contain optionals.\n" + + "If you did not mean to use an optional you can use '\\(' to escape the the '('"))); } @Test @@ -249,8 +261,8 @@ public void does_not_allow_optional_parameter_types() { "\n" + "({int})\n" + " ^---^\n" + - "Parameter types cannot be optional.\n" + - "If you did not mean to use an alternative you can use '\\(' to escape the the '('"))); + "An optional may not contain a parameter type.\n" + + "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'"))); } @Test @@ -269,8 +281,8 @@ public void does_not_allow_text_parameter_type_alternation() { "\n" + "x/{int}\n" + " ^---^\n" + - "Parameter types cannot be alternative.\n" + - "If you did not mean to use an alternative you can use '\\{' to escape the the '{'"))); + "An alternative may not contain a parameter type.\n" + + "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'"))); } @Test @@ -284,8 +296,8 @@ public void does_not_allow_parameter_type_text_alternation() { "\n" + "{int}/x\n" + "^---^\n" + - "Parameter types cannot be alternative.\n" + - "If you did not mean to use an alternative you can use '\\{' to escape the the '{'"))); + "An alternative may not contain a parameter type.\n" + + "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'"))); } @Test From d77a27816baef6a5ec9b54dec7603f07978d5dae Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 17 Jul 2020 16:01:29 +0200 Subject: [PATCH 079/183] Reduce unused exception to validation --- .../cucumberexpressions/Argument.java | 8 ++--- .../CucumberExpression.java | 2 +- .../CucumberExpressionException.java | 34 ------------------- .../RegularExpression.java | 2 +- .../cucumberexpressions/ArgumentTest.java | 6 ++-- 5 files changed, 9 insertions(+), 43 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Argument.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Argument.java index 7daf274185..3dd15413bc 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Argument.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Argument.java @@ -6,18 +6,18 @@ import java.util.ArrayList; import java.util.List; -import static io.cucumber.cucumberexpressions.CucumberExpressionException.createCaptureGroupParameterTypeMisMatch; - @API(status = API.Status.STABLE) public final class Argument { private final ParameterType parameterType; private final Group group; - static List> build(Group group, TreeRegexp treeRegexp, List> parameterTypes) { + static List> build(Group group, List> parameterTypes) { List argGroups = group.getChildren(); if (argGroups.size() != parameterTypes.size()) { - throw createCaptureGroupParameterTypeMisMatch(treeRegexp, parameterTypes, argGroups); + // This requires regex injection through a Cucumber expression. + // Regex injection should be be possible any more. + throw new IllegalArgumentException(String.format("Group has %s capture groups, but there were %s parameter types", argGroups.size(), parameterTypes.size())); } List> args = new ArrayList<>(argGroups.size()); for (int i = 0; i < parameterTypes.size(); i++) { diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index f6d82d2a2a..a0b9ccab99 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -149,7 +149,7 @@ public List> match(String text, Type... typeHints) { } } - return Argument.build(group, treeRegexp, parameterTypes); + return Argument.build(group, parameterTypes); } @Override diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index f5b93d745b..40bb8432d3 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -6,9 +6,6 @@ import io.cucumber.cucumberexpressions.Ast.Token.Type; import org.apiguardian.api.API; -import java.util.ArrayList; -import java.util.List; - @API(status = API.Status.STABLE) public class CucumberExpressionException extends RuntimeException { @@ -20,36 +17,6 @@ public class CucumberExpressionException extends RuntimeException { super(message, cause); } - static CucumberExpressionException createCaptureGroupParameterTypeMisMatch(TreeRegexp treeRegexp, - List> parameterTypes, List argGroups) { - return new CucumberExpressionException( - String.format("Expression /%s/ has %s capture groups (%s), but there were %s parameter types (%s)", - treeRegexp.pattern().pattern(), - argGroups.size(), - getGroupValues(argGroups), - parameterTypes.size(), - getParameterTypeNames(parameterTypes) - )); - } - - private static List getParameterTypeNames(List> parameterTypes) { - List list = new ArrayList<>(); - for (ParameterType type : parameterTypes) { - String name = type.getName(); - list.add(name); - } - return list; - } - - private static List getGroupValues(List argGroups) { - List list = new ArrayList<>(); - for (Group argGroup : argGroups) { - String value = argGroup.getValue(); - list.add(value); - } - return list; - } - static CucumberExpressionException createMissingEndTokenException(String expression, Type beginToken, Type endToken, Token current) { return new CucumberExpressionException(message( @@ -160,5 +127,4 @@ private static StringBuilder pointAt(int index) { return pointer; } - } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/RegularExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/RegularExpression.java index 4c0b4f0ca5..d13f8de1fb 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/RegularExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/RegularExpression.java @@ -69,7 +69,7 @@ public List> match(String text, Type... typeHints) { parameterTypes.add(parameterType); } - return Argument.build(group, treeRegexp, parameterTypes); + return Argument.build(group, parameterTypes); } @Override diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ArgumentTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ArgumentTest.java index 753bd10817..0404d02506 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ArgumentTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ArgumentTest.java @@ -2,10 +2,10 @@ import org.junit.jupiter.api.Test; -import java.util.Collections; import java.util.List; import java.util.Locale; +import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; public class ArgumentTest { @@ -15,9 +15,9 @@ public void exposes_parameter_type() { ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); List> arguments = Argument.build( treeRegexp.match("three blind mice"), - treeRegexp, - Collections.singletonList(parameterTypeRegistry.lookupByTypeName("string"))); + singletonList(parameterTypeRegistry.lookupByTypeName("string"))); Argument argument = arguments.get(0); assertEquals("string", argument.getParameterType().getName()); } + } From a52d07da5e29707730ba4394c5c05057918eec76 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 17 Jul 2020 17:54:30 +0200 Subject: [PATCH 080/183] Structure and naming --- .../io/cucumber/cucumberexpressions/Ast.java | 47 ++-- .../CucumberExpression.java | 30 +-- .../CucumberExpressionException.java | 12 +- .../CucumberExpressionParser.java | 56 ++-- .../CucumberExpressionTokenizer.java | 222 ++++++++-------- .../CucumberExpressionParserTest.java | 243 +++++++++--------- 6 files changed, 317 insertions(+), 293 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 83df65b490..441826d51b 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -10,32 +10,39 @@ final class Ast { + static final char ESCAPE_CHARACTER = '\\'; + static final char ALTERNATION_CHARACTER = '/'; + static final char BEGIN_PARAMETER_CHARACTER = '{'; + static final char END_PARAMETER_CHARACTER = '}'; + static final char BEGIN_OPTIONAL_CHARACTER = '('; + static final char END_OPTIONAL_CHARACTER = ')'; + interface Located { int start(); int end(); } - static final class AstNode implements Located { + static final class Node implements Located { private final Type type; - private final List nodes; + private final List nodes; private final String token; private final int startIndex; private final int endIndex; - AstNode(Type type, int startIndex, int endIndex, String token) { + Node(Type type, int startIndex, int endIndex, String token) { this(type, startIndex, endIndex, null, token); } - AstNode(Type type, int startIndex, int endIndex, AstNode... nodes) { + Node(Type type, int startIndex, int endIndex, Node... nodes) { this(type, startIndex, endIndex, asList(nodes)); } - AstNode(Type type, int startIndex, int endIndex, List nodes) { + Node(Type type, int startIndex, int endIndex, List nodes) { this(type, startIndex, endIndex, nodes, null); } - private AstNode(Type type, int startIndex, int endIndex, List nodes, String token) { + private Node(Type type, int startIndex, int endIndex, List nodes, String token) { this.type = requireNonNull(type); this.nodes = nodes; this.token = token; @@ -60,7 +67,7 @@ public int end(){ return endIndex; } - List nodes() { + List nodes() { return nodes; } @@ -77,7 +84,7 @@ String text() { return token; return nodes().stream() - .map(AstNode::text) + .map(Node::text) .collect(joining()); } @@ -99,7 +106,7 @@ private StringBuilder toString(int depth) { if (nodes != null) { sb.append("\n"); - for (AstNode node : nodes) { + for (Node node : nodes) { sb.append(node.toString(depth + 1)); sb.append("\n"); } @@ -118,12 +125,12 @@ public boolean equals(Object o) { return true; if (o == null || getClass() != o.getClass()) return false; - AstNode astNode = (AstNode) o; - return startIndex == astNode.startIndex && - endIndex == astNode.endIndex && - type == astNode.type && - Objects.equals(nodes, astNode.nodes) && - Objects.equals(token, astNode.token); + Node node = (Node) o; + return startIndex == node.startIndex && + endIndex == node.endIndex && + type == node.type && + Objects.equals(nodes, node.nodes) && + Objects.equals(token, node.token); } @Override @@ -186,11 +193,11 @@ enum Type { START_OF_LINE, END_OF_LINE, WHITE_SPACE, - BEGIN_OPTIONAL("(", "optional text"), - END_OPTIONAL(")", "optional text"), - BEGIN_PARAMETER("{", "a parameter"), - END_PARAMETER("}", "a parameter"), - ALTERNATION("/", "alternation"), + BEGIN_OPTIONAL(""+ BEGIN_OPTIONAL_CHARACTER, "optional text"), + END_OPTIONAL("" + END_OPTIONAL_CHARACTER, "optional text"), + BEGIN_PARAMETER("" + BEGIN_PARAMETER_CHARACTER, "a parameter"), + END_PARAMETER("" + END_PARAMETER_CHARACTER, "a parameter"), + ALTERNATION("" + ALTERNATION_CHARACTER, "alternation"), TEXT; private final String symbol; diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index a0b9ccab99..3a586d5eaa 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -1,6 +1,6 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.Ast.AstNode; +import io.cucumber.cucumberexpressions.Ast.Node; import org.apiguardian.api.API; import java.lang.reflect.Type; @@ -9,8 +9,8 @@ import java.util.function.Function; import java.util.regex.Pattern; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeIsEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayExclusivelyContainOptionals; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty; @@ -32,12 +32,12 @@ public final class CucumberExpression implements Expression { this.parameterTypeRegistry = parameterTypeRegistry; CucumberExpressionParser parser = new CucumberExpressionParser(); - AstNode ast = parser.parse(expression); + Node ast = parser.parse(expression); String pattern = rewriteToRegex(ast); treeRegexp = new TreeRegexp(pattern); } - private String rewriteToRegex(AstNode node) { + private String rewriteToRegex(Node node) { switch (node.type()) { case TEXT_NODE: return escapeRegex(node.text()); @@ -60,7 +60,7 @@ private static String escapeRegex(String text) { return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$1"); } - private String rewriteOptional(AstNode node) { + private String rewriteOptional(Node node) { assertNoParameters(node, astNode -> createParameterIsNotAllowedInOptional(astNode, source)); assertNotEmpty(node, astNode -> createOptionalMayNotBeEmpty(astNode, source)); return node.nodes().stream() @@ -68,9 +68,9 @@ private String rewriteOptional(AstNode node) { .collect(joining("", "(?:", ")?")); } - private String rewriteAlternation(AstNode node) { + private String rewriteAlternation(Node node) { // Make sure the alternative parts aren't empty and don't contain parameter types - for (AstNode alternative : node.nodes()) { + for (Node alternative : node.nodes()) { if (alternative.nodes().isEmpty()) { throw createAlternativeIsEmpty(alternative, source); } @@ -83,13 +83,13 @@ private String rewriteAlternation(AstNode node) { .collect(joining("|", "(?:", ")")); } - private String rewriteAlternative(AstNode node) { + private String rewriteAlternative(Node node) { return node.nodes().stream() .map(this::rewriteToRegex) .collect(joining()); } - private String rewriteParameter(AstNode node) { + private String rewriteParameter(Node node) { String name = node.text(); ParameterType.checkParameterTypeName(name); ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); @@ -105,14 +105,14 @@ private String rewriteParameter(AstNode node) { .collect(joining(")|(?:", "((?:", "))")); } - private String rewriteExpression(AstNode node) { + private String rewriteExpression(Node node) { return node.nodes().stream() .map(this::rewriteToRegex) .collect(joining("", "^", "$")); } - private void assertNotEmpty(AstNode node, - Function createNodeWasNotEmptyException) { + private void assertNotEmpty(Node node, + Function createNodeWasNotEmptyException) { node.nodes() .stream() .filter(astNode -> TEXT_NODE.equals(astNode.type())) @@ -120,8 +120,8 @@ private void assertNotEmpty(AstNode node, .orElseThrow(() -> createNodeWasNotEmptyException.apply(node)); } - private void assertNoParameters(AstNode node, - Function createNodeContainedAParameterException) { + private void assertNoParameters(Node node, + Function createNodeContainedAParameterException) { node.nodes() .stream() .filter(astNode -> PARAMETER_NODE.equals(astNode.type())) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 40bb8432d3..e766093d68 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -1,6 +1,6 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.Ast.AstNode; +import io.cucumber.cucumberexpressions.Ast.Node; import io.cucumber.cucumberexpressions.Ast.Located; import io.cucumber.cucumberexpressions.Ast.Token; import io.cucumber.cucumberexpressions.Ast.Token.Type; @@ -38,7 +38,7 @@ static CucumberExpressionException createTheEndOfLineCanNotBeEscapedException(St )); } - static CucumberExpressionException createAlternativeIsEmpty(AstNode node, String expression) { + static CucumberExpressionException createAlternativeIsEmpty(Node node, String expression) { return new CucumberExpressionException(message( node.start(), expression, @@ -47,7 +47,7 @@ static CucumberExpressionException createAlternativeIsEmpty(AstNode node, String "If you did not mean to use an alternative you can use '\\/' to escape the the '/'")); } - static CucumberExpressionException createParameterIsNotAllowedInAlternative(AstNode node, String expression) { + static CucumberExpressionException createParameterIsNotAllowedInAlternative(Node node, String expression) { return new CucumberExpressionException(message( node.start(), expression, @@ -56,7 +56,7 @@ static CucumberExpressionException createParameterIsNotAllowedInAlternative(AstN "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'")); } - static CucumberExpressionException createParameterIsNotAllowedInOptional(AstNode node, String expression) { + static CucumberExpressionException createParameterIsNotAllowedInOptional(Node node, String expression) { return new CucumberExpressionException(message( node.start(), expression, @@ -65,7 +65,7 @@ static CucumberExpressionException createParameterIsNotAllowedInOptional(AstNode "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'")); } - static CucumberExpressionException createOptionalMayNotBeEmpty(AstNode node, String expression) { + static CucumberExpressionException createOptionalMayNotBeEmpty(Node node, String expression) { return new CucumberExpressionException(message( node.start(), expression, @@ -74,7 +74,7 @@ static CucumberExpressionException createOptionalMayNotBeEmpty(AstNode node, Str "If you did not mean to use an optional you can use '\\(' to escape the the '('")); } - static CucumberExpressionException createAlternativeMayExclusivelyContainOptionals(AstNode node, + static CucumberExpressionException createAlternativeMayExclusivelyContainOptionals(Node node, String expression) { return new CucumberExpressionException(message( node.start(), diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 649cb27aea..0d5110efb1 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -1,6 +1,6 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.Ast.AstNode; +import io.cucumber.cucumberexpressions.Ast.Node; import io.cucumber.cucumberexpressions.Ast.Token; import io.cucumber.cucumberexpressions.Ast.Token.Type; @@ -8,12 +8,12 @@ import java.util.Arrays; import java.util.List; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATION_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATIVE_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.EXPRESSION_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.OPTIONAL_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATION_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATIVE_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.EXPRESSION_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.OPTIONAL_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; @@ -33,7 +33,7 @@ final class CucumberExpressionParser { */ private static final Parser textParser = (expression, tokens, current) -> { Token token = tokens.get(current); - return new Result(1, new AstNode(TEXT_NODE, token.start(), token.end(), token.text)); + return new Result(1, new Node(TEXT_NODE, token.start(), token.end(), token.text)); }; /* @@ -63,7 +63,7 @@ final class CucumberExpressionParser { return new Result(0); } Token token = tokens.get(current); - return new Result(1, new AstNode(ALTERNATIVE_NODE, token.start(), token.end(), "/")); + return new Result(1, new Node(ALTERNATIVE_NODE, token.start(), token.end(), "/")); }; private static final List alternativeParsers = asList( @@ -93,7 +93,7 @@ final class CucumberExpressionParser { int end = tokens.get(subCurrent).start(); // Does not consume right hand boundary token return new Result(result.consumed, - new AstNode(ALTERNATION_NODE, start, end, splitAlternatives(start, end, result.ast))); + new Node(ALTERNATION_NODE, start, end, splitAlternatives(start, end, result.ast))); }; /* @@ -111,7 +111,7 @@ final class CucumberExpressionParser { ) ); - AstNode parse(String expression) { + Node parse(String expression) { CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); List tokens = tokenizer.tokenize(expression); Result result = cucumberExpressionParser.parse(expression, tokens, 0); @@ -125,13 +125,13 @@ private interface Parser { private static final class Result { final int consumed; - final List ast; + final List ast; - private Result(int consumed, AstNode... ast) { + private Result(int consumed, Node... ast) { this(consumed, Arrays.asList(ast)); } - private Result(int consumed, List ast) { + private Result(int consumed, List ast) { this.consumed = consumed; this.ast = ast; } @@ -139,7 +139,7 @@ private Result(int consumed, List ast) { } private static Parser parseBetween( - AstNode.Type type, + Node.Type type, Type beginToken, Type endToken, List parsers) { @@ -158,7 +158,7 @@ private static Parser parseBetween( // consumes endToken int start = tokens.get(current).start(); int end = tokens.get(subCurrent).end(); - return new Result(subCurrent + 1 - current, new AstNode(type, start, end, result.ast)); + return new Result(subCurrent + 1 - current, new Node(type, start, end, result.ast)); }; } @@ -170,7 +170,7 @@ private static Result parseTokensUntil( Type... endTokens) { int current = startAt; int size = tokens.size(); - List ast = new ArrayList<>(); + List ast = new ArrayList<>(); while (current < size) { if (lookingAt(tokens, current, endTokens)) { break; @@ -220,12 +220,12 @@ private static boolean lookingAt(List tokens, int at, Type token) { return tokens.get(at).type == token; } - private static List splitAlternatives(int start, int end, List astNode) { + private static List splitAlternatives(int start, int end, List node) { - List seperators = new ArrayList<>(); - List> alternatives = new ArrayList<>(); - List alternative = new ArrayList<>(); - for (AstNode token : astNode) { + List seperators = new ArrayList<>(); + List> alternatives = new ArrayList<>(); + List alternative = new ArrayList<>(); + for (Node token : node) { if (ALTERNATIVE_NODE.equals(token.type())) { seperators.add(token); alternatives.add(alternative); @@ -236,16 +236,16 @@ private static List splitAlternatives(int start, int end, List } alternatives.add(alternative); - List alts = new ArrayList<>(); + List alts = new ArrayList<>(); for (int i = 0; i < alternatives.size(); i++) { - List astNodes = alternatives.get(i); + List nodes = alternatives.get(i); if (i == 0) { - alts.add(new AstNode(ALTERNATIVE_NODE, start, seperators.get(i).start(), astNodes)); + alts.add(new Node(ALTERNATIVE_NODE, start, seperators.get(i).start(), nodes)); } else if (i == alternatives.size() - 1) { - alts.add(new AstNode(ALTERNATIVE_NODE, seperators.get(i - 1).end(), end, astNodes)); + alts.add(new Node(ALTERNATIVE_NODE, seperators.get(i - 1).end(), end, nodes)); } else { - alts.add(new AstNode(ALTERNATIVE_NODE, seperators.get(i - 1).end(), seperators.get(i).start(), - astNodes)); + alts.add(new Node(ALTERNATIVE_NODE, seperators.get(i - 1).end(), seperators.get(i).start(), + nodes)); } } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index d745ec8b63..922ee3b3f2 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -21,123 +21,141 @@ List tokenize(String expression) { } private Iterable tokenizeImpl(String expression) { - return () -> new Iterator() { - final OfInt codePoints = expression.codePoints().iterator(); - StringBuilder buffer = new StringBuilder(); - Type previousTokenType = null; - Type currentTokenType = Type.START_OF_LINE; - boolean treatAsText = false; - int index = 0; - int escaped = 0; - - @Override - public boolean hasNext() { - return previousTokenType != Type.END_OF_LINE; - } + return () -> new TokenIterator(expression); - @Override - public Token next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - if (currentTokenType == Type.START_OF_LINE) { - Token token = convertBufferToToken(currentTokenType); - advanceTokenTypes(); - return token; - } + } + + private static class TokenIterator implements Iterator { + final OfInt codePoints; + private final String expression; + StringBuilder buffer; + Type previousTokenType; + Type currentTokenType; + boolean treatAsText; + int index; + int escaped; + + public TokenIterator(String expression) { + this.expression = expression; + codePoints = expression.codePoints().iterator(); + buffer = new StringBuilder(); + previousTokenType = null; + currentTokenType = Type.START_OF_LINE; + treatAsText = false; + index = 0; + escaped = 0; + } + + @Override + public boolean hasNext() { + return previousTokenType != Type.END_OF_LINE; + } + + @Override + public Token next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (currentTokenType == Type.START_OF_LINE) { + Token token = convertBufferToToken(currentTokenType); + advanceTokenTypes(); + return token; + } - while (codePoints.hasNext()) { - int token = codePoints.nextInt(); - if (!treatAsText && token == '\\') { - escaped++; - treatAsText = true; - continue; - } - currentTokenType = tokenTypeOf(token, treatAsText); - treatAsText = false; - - if (previousTokenType != Type.START_OF_LINE - && (currentTokenType != previousTokenType - || (currentTokenType != Type.WHITE_SPACE && currentTokenType != Type.TEXT))) { - Token t = convertBufferToToken(previousTokenType); - advanceTokenTypes(); - buffer.appendCodePoint(token); - return t; - } else { - advanceTokenTypes(); - buffer.appendCodePoint(token); - } + while (codePoints.hasNext()) { + int token = codePoints.nextInt(); + if (!treatAsText && token == Ast.ESCAPE_CHARACTER) { + escaped++; + treatAsText = true; + continue; } + currentTokenType = tokenTypeOf(token, treatAsText); + treatAsText = false; - if (buffer.length() > 0) { - Token token = convertBufferToToken(previousTokenType); + if (previousTokenType != Type.START_OF_LINE + && (currentTokenType != previousTokenType + || (currentTokenType != Type.WHITE_SPACE && currentTokenType != Type.TEXT))) { + Token t = convertBufferToToken(previousTokenType); advanceTokenTypes(); - return token; + buffer.appendCodePoint(token); + return t; + } else { + advanceTokenTypes(); + buffer.appendCodePoint(token); } + } - currentTokenType = Type.END_OF_LINE; - if (treatAsText) { - throw createTheEndOfLineCanNotBeEscapedException(expression); - } - Token token = convertBufferToToken(currentTokenType); + if (buffer.length() > 0) { + Token token = convertBufferToToken(previousTokenType); advanceTokenTypes(); return token; } - private void advanceTokenTypes() { - previousTokenType = currentTokenType; - currentTokenType = null; + currentTokenType = Type.END_OF_LINE; + if (treatAsText) { + throw createTheEndOfLineCanNotBeEscapedException(expression); } - - private Token convertBufferToToken(Type currentTokenType) { - int escapeTokens = 0; - if (currentTokenType == Type.TEXT) { - escapeTokens = escaped; - escaped = 0; - } - int endIndex = index + buffer.codePointCount(0, buffer.length()) + escapeTokens; - Token t = new Token(buffer.toString(), currentTokenType, index, endIndex); - buffer = new StringBuilder(); - this.index = endIndex; - return t; + Token token = convertBufferToToken(currentTokenType); + advanceTokenTypes(); + return token; + } + + private void advanceTokenTypes() { + previousTokenType = currentTokenType; + currentTokenType = null; + } + + private Token convertBufferToToken(Type currentTokenType) { + int escapeTokens = 0; + if (currentTokenType == Type.TEXT) { + escapeTokens = escaped; + escaped = 0; } - - private Type tokenTypeOf(Integer token, boolean treatAsText) { - if (treatAsText) { - switch (token) { - case (int) '\\': - case (int) '/': - case (int) '{': - case (int) '}': - case (int) '(': - case (int) ')': - return Type.TEXT; - } - if (Character.isWhitespace(token)) { - return Type.TEXT; - } - throw createCantEscape(expression, index + escaped); - } - - if (Character.isWhitespace(token)) { - return Type.WHITE_SPACE; - } - - switch (token) { - case (int) '/': - return Type.ALTERNATION; - case (int) '{': - return Type.BEGIN_PARAMETER; - case (int) '}': - return Type.END_PARAMETER; - case (int) '(': - return Type.BEGIN_OPTIONAL; - case (int) ')': - return Type.END_OPTIONAL; - } + int endIndex = index + buffer.codePointCount(0, buffer.length()) + escapeTokens; + Token t = new Token(buffer.toString(), currentTokenType, index, endIndex); + buffer = new StringBuilder(); + this.index = endIndex; + return t; + } + + private Type tokenTypeOf(Integer token, boolean treatAsText) { + return treatAsText ? textTokenTypeOf(token) : tokenTypeOf(token); + } + + private Type textTokenTypeOf(Integer token) { + if (Character.isWhitespace(token)) { return Type.TEXT; } - }; + switch (token) { + case (int) Ast.ESCAPE_CHARACTER: + case (int) Ast.ALTERNATION_CHARACTER: + case (int) Ast.BEGIN_PARAMETER_CHARACTER: + case (int) Ast.END_PARAMETER_CHARACTER: + case (int) Ast.BEGIN_OPTIONAL_CHARACTER: + case (int) Ast.END_OPTIONAL_CHARACTER: + return Type.TEXT; + } + throw createCantEscape(expression, index + escaped); + } + + private Type tokenTypeOf(Integer token) { + if (Character.isWhitespace(token)) { + return Type.WHITE_SPACE; + } + switch (token) { + case (int) Ast.ALTERNATION_CHARACTER: + return Type.ALTERNATION; + case (int) Ast.BEGIN_PARAMETER_CHARACTER: + return Type.BEGIN_PARAMETER; + case (int) Ast.END_PARAMETER_CHARACTER: + return Type.END_PARAMETER; + case (int) Ast.BEGIN_OPTIONAL_CHARACTER: + return Type.BEGIN_OPTIONAL; + case (int) Ast.END_OPTIONAL_CHARACTER: + return Type.END_OPTIONAL; + } + return Type.TEXT; + } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index f300dfb1dd..dc113d7735 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -1,15 +1,14 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.Ast.AstNode; -import org.junit.jupiter.api.Assertions; +import io.cucumber.cucumberexpressions.Ast.Node; import org.junit.jupiter.api.Test; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATION_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.ALTERNATIVE_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.EXPRESSION_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.OPTIONAL_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.PARAMETER_NODE; -import static io.cucumber.cucumberexpressions.Ast.AstNode.Type.TEXT_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATION_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATIVE_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.EXPRESSION_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.OPTIONAL_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -23,19 +22,19 @@ class CucumberExpressionParserTest { @Test void emptyString() { assertThat(astOf(""), equalTo( - new AstNode(EXPRESSION_NODE, 0, 0) + new Node(EXPRESSION_NODE, 0, 0) )); } @Test void phrase() { assertThat(astOf("three blind mice"), equalTo( - new AstNode(EXPRESSION_NODE, 0, 16, - new AstNode(TEXT_NODE, 0, 5, "three"), - new AstNode(TEXT_NODE, 5, 6, " "), - new AstNode(TEXT_NODE, 6, 11, "blind"), - new AstNode(TEXT_NODE, 11, 12, " "), - new AstNode(TEXT_NODE, 12, 16, "mice") + new Node(EXPRESSION_NODE, 0, 16, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(TEXT_NODE, 6, 11, "blind"), + new Node(TEXT_NODE, 11, 12, " "), + new Node(TEXT_NODE, 12, 16, "mice") ) )); } @@ -43,9 +42,9 @@ void phrase() { @Test void optional() { assertThat(astOf("(blind)"), equalTo( - new AstNode(EXPRESSION_NODE, 0, 7, - new AstNode(OPTIONAL_NODE, 0, 7, - new AstNode(TEXT_NODE, 1, 6, "blind") + new Node(EXPRESSION_NODE, 0, 7, + new Node(OPTIONAL_NODE, 0, 7, + new Node(TEXT_NODE, 1, 6, "blind") ) ) )); @@ -54,9 +53,9 @@ void optional() { @Test void parameter() { assertThat(astOf("{string}"), equalTo( - new AstNode(EXPRESSION_NODE, 0, 8, - new AstNode(PARAMETER_NODE, 0, 8, - new AstNode(TEXT_NODE, 1, 7, "string") + new Node(EXPRESSION_NODE, 0, 8, + new Node(PARAMETER_NODE, 0, 8, + new Node(TEXT_NODE, 1, 7, "string") ) ) )); @@ -65,8 +64,8 @@ void parameter() { @Test void anonymousParameter() { assertThat(astOf("{}"), equalTo( - new AstNode(EXPRESSION_NODE, 0, 2, - new AstNode(PARAMETER_NODE, 0, 2) + new Node(EXPRESSION_NODE, 0, 2, + new Node(PARAMETER_NODE, 0, 2) ) )); } @@ -75,14 +74,14 @@ void anonymousParameter() { @Test void optionalPhrase() { assertThat(astOf("three (blind) mice"), equalTo( - new AstNode(EXPRESSION_NODE,0,18, - new AstNode(TEXT_NODE, 0, 5, "three"), - new AstNode(TEXT_NODE, 5, 6, " "), - new AstNode(OPTIONAL_NODE, 6, 13, - new AstNode(TEXT_NODE, 7, 12, "blind") + new Node(EXPRESSION_NODE,0,18, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(OPTIONAL_NODE, 6, 13, + new Node(TEXT_NODE, 7, 12, "blind") ), - new AstNode(TEXT_NODE, 13, 14, " "), - new AstNode(TEXT_NODE, 14, 18, "mice") + new Node(TEXT_NODE, 13, 14, " "), + new Node(TEXT_NODE, 14, 18, "mice") ) )); } @@ -112,8 +111,8 @@ void escapeNonReservedCharacter() { @Test void escapedBackSlash() { assertThat(astOf("\\\\"), equalTo( - new AstNode(EXPRESSION_NODE,0,2, - new AstNode(TEXT_NODE, 0, 2, "\\") + new Node(EXPRESSION_NODE,0,2, + new Node(TEXT_NODE, 0, 2, "\\") ) )); } @@ -133,8 +132,8 @@ void openingBrace() { @Test void closingBrace() { assertThat(astOf("}"), equalTo( - new AstNode(EXPRESSION_NODE,0,1, - new AstNode(TEXT_NODE, 0, 1, "}") + new Node(EXPRESSION_NODE,0,1, + new Node(TEXT_NODE, 0, 1, "}") ) )); } @@ -165,8 +164,8 @@ void openingParenthesis() { @Test void closingParenthesis() { assertThat(astOf(")"), equalTo( - new AstNode(EXPRESSION_NODE,0,1, - new AstNode(TEXT_NODE, 0, 1, ")") + new Node(EXPRESSION_NODE,0,1, + new Node(TEXT_NODE, 0, 1, ")") ) )); } @@ -174,8 +173,8 @@ void closingParenthesis() { @Test void escapedOpeningParenthesis() { assertThat(astOf("\\("), equalTo( - new AstNode(EXPRESSION_NODE,0,2, - new AstNode(TEXT_NODE, 0, 2, "(") + new Node(EXPRESSION_NODE,0,2, + new Node(TEXT_NODE, 0, 2, "(") ) )); } @@ -183,9 +182,9 @@ void escapedOpeningParenthesis() { @Test void escapedOptional() { assertThat(astOf("\\(blind)"), equalTo( - new AstNode(EXPRESSION_NODE,0,8, - new AstNode(TEXT_NODE, 0, 7, "(blind"), - new AstNode(TEXT_NODE, 7, 8, ")") + new Node(EXPRESSION_NODE,0,8, + new Node(TEXT_NODE, 0, 7, "(blind"), + new Node(TEXT_NODE, 7, 8, ")") ) )); } @@ -193,13 +192,13 @@ void escapedOptional() { @Test void escapedOptionalPhrase() { assertThat(astOf("three \\(blind) mice"), equalTo( - new AstNode(EXPRESSION_NODE,0,19, - new AstNode(TEXT_NODE, 0, 5, "three"), - new AstNode(TEXT_NODE, 5, 6, " "), - new AstNode(TEXT_NODE, 6, 13, "(blind"), - new AstNode(TEXT_NODE, 13, 14, ")"), - new AstNode(TEXT_NODE, 14, 15, " "), - new AstNode(TEXT_NODE, 15, 19, "mice") + new Node(EXPRESSION_NODE,0,19, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(TEXT_NODE, 6, 13, "(blind"), + new Node(TEXT_NODE, 13, 14, ")"), + new Node(TEXT_NODE, 14, 15, " "), + new Node(TEXT_NODE, 15, 19, "mice") ) )); } @@ -207,18 +206,18 @@ void escapedOptionalPhrase() { @Test void escapedOptionalFollowedByOptional() { assertThat(astOf("three \\((very) blind) mice"), equalTo( - new AstNode(EXPRESSION_NODE, 0, 26, - new AstNode(TEXT_NODE, 0, 5, "three"), - new AstNode(TEXT_NODE, 5, 6, " "), - new AstNode(TEXT_NODE, 6, 8, "("), - new AstNode(OPTIONAL_NODE, 8, 14, - new AstNode(TEXT_NODE, 9, 13, "very") + new Node(EXPRESSION_NODE, 0, 26, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(TEXT_NODE, 6, 8, "("), + new Node(OPTIONAL_NODE, 8, 14, + new Node(TEXT_NODE, 9, 13, "very") ), - new AstNode(TEXT_NODE, 14, 15, " "), - new AstNode(TEXT_NODE, 15, 20, "blind"), - new AstNode(TEXT_NODE, 20, 21, ")"), - new AstNode(TEXT_NODE, 21, 22, " "), - new AstNode(TEXT_NODE, 22, 26, "mice") + new Node(TEXT_NODE, 14, 15, " "), + new Node(TEXT_NODE, 15, 20, "blind"), + new Node(TEXT_NODE, 20, 21, ")"), + new Node(TEXT_NODE, 21, 22, " "), + new Node(TEXT_NODE, 22, 26, "mice") ) )); } @@ -226,17 +225,17 @@ void escapedOptionalFollowedByOptional() { @Test void optionalContainingEscapedOptional() { assertThat(astOf("three ((very\\) blind) mice"), equalTo( - new AstNode(EXPRESSION_NODE, 0, 26, - new AstNode(TEXT_NODE, 0, 5, "three"), - new AstNode(TEXT_NODE, 5, 6, " "), - new AstNode(OPTIONAL_NODE, 6,21, - new AstNode(TEXT_NODE, 7, 8, "("), - new AstNode(TEXT_NODE, 8, 14, "very)"), - new AstNode(TEXT_NODE, 14, 15, " "), - new AstNode(TEXT_NODE, 15, 20, "blind") + new Node(EXPRESSION_NODE, 0, 26, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(OPTIONAL_NODE, 6,21, + new Node(TEXT_NODE, 7, 8, "("), + new Node(TEXT_NODE, 8, 14, "very)"), + new Node(TEXT_NODE, 14, 15, " "), + new Node(TEXT_NODE, 15, 20, "blind") ), - new AstNode(TEXT_NODE, 21, 22, " "), - new AstNode(TEXT_NODE, 22, 26, "mice") + new Node(TEXT_NODE, 21, 22, " "), + new Node(TEXT_NODE, 22, 26, "mice") ) )); } @@ -244,13 +243,13 @@ void optionalContainingEscapedOptional() { @Test void alternation() { assertThat(astOf("mice/rats"), equalTo( - new AstNode(EXPRESSION_NODE, 0,9, - new AstNode(ALTERNATION_NODE, 0,9, - new AstNode(ALTERNATIVE_NODE,0,4, - new AstNode(TEXT_NODE, 0, 4, "mice") + new Node(EXPRESSION_NODE, 0,9, + new Node(ALTERNATION_NODE, 0,9, + new Node(ALTERNATIVE_NODE,0,4, + new Node(TEXT_NODE, 0, 4, "mice") ), - new AstNode(ALTERNATIVE_NODE, 5,9, - new AstNode(TEXT_NODE, 5, 9, "rats") + new Node(ALTERNATIVE_NODE, 5,9, + new Node(TEXT_NODE, 5, 9, "rats") ) ) ) @@ -260,10 +259,10 @@ void alternation() { @Test void emptyAlternation() { assertThat(astOf("/"), equalTo( - new AstNode(EXPRESSION_NODE,0,1, - new AstNode(ALTERNATION_NODE,0,1, - new AstNode(ALTERNATIVE_NODE, 0,0), - new AstNode(ALTERNATIVE_NODE, 1,1) + new Node(EXPRESSION_NODE,0,1, + new Node(ALTERNATION_NODE,0,1, + new Node(ALTERNATIVE_NODE, 0,0), + new Node(ALTERNATIVE_NODE, 1,1) ) ) )); @@ -272,11 +271,11 @@ void emptyAlternation() { @Test void emptyAlternations() { assertThat(astOf("//"), equalTo( - new AstNode(EXPRESSION_NODE,0,2, - new AstNode(ALTERNATION_NODE, 0,2, - new AstNode(ALTERNATIVE_NODE, 0,0), - new AstNode(ALTERNATIVE_NODE, 1,1), - new AstNode(ALTERNATIVE_NODE, 2,2) + new Node(EXPRESSION_NODE,0,2, + new Node(ALTERNATION_NODE, 0,2, + new Node(ALTERNATIVE_NODE, 0,0), + new Node(ALTERNATIVE_NODE, 1,1), + new Node(ALTERNATIVE_NODE, 2,2) ) ) )); @@ -285,8 +284,8 @@ void emptyAlternations() { @Test void escapedAlternation() { assertThat(astOf("mice\\/rats"), equalTo( - new AstNode(EXPRESSION_NODE, 0, 10, - new AstNode(TEXT_NODE, 0, 10, "mice/rats") + new Node(EXPRESSION_NODE, 0, 10, + new Node(TEXT_NODE, 0, 10, "mice/rats") ) )); } @@ -294,19 +293,19 @@ void escapedAlternation() { @Test void alternationPhrase() { assertThat(astOf("three hungry/blind mice"), equalTo( - new AstNode(EXPRESSION_NODE, 0, 23, - new AstNode(TEXT_NODE, 0, 5, "three"), - new AstNode(TEXT_NODE, 5, 6, " "), - new AstNode(ALTERNATION_NODE, 6,18, - new AstNode(ALTERNATIVE_NODE, 6, 12, - new AstNode(TEXT_NODE, 6, 12, "hungry") + new Node(EXPRESSION_NODE, 0, 23, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(ALTERNATION_NODE, 6,18, + new Node(ALTERNATIVE_NODE, 6, 12, + new Node(TEXT_NODE, 6, 12, "hungry") ), - new AstNode(ALTERNATIVE_NODE, 13, 18, - new AstNode(TEXT_NODE, 13, 18, "blind") + new Node(ALTERNATIVE_NODE, 13, 18, + new Node(TEXT_NODE, 13, 18, "blind") ) ), - new AstNode(TEXT_NODE, 18, 19, " "), - new AstNode(TEXT_NODE, 19, 23, "mice") + new Node(TEXT_NODE, 18, 19, " "), + new Node(TEXT_NODE, 19, 23, "mice") ) )); } @@ -314,13 +313,13 @@ void alternationPhrase() { @Test void alternationWithWhiteSpace() { assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( - new AstNode(EXPRESSION_NODE, 0, 29, - new AstNode(ALTERNATION_NODE, 0,29, - new AstNode(ALTERNATIVE_NODE,0,15, - new AstNode(TEXT_NODE, 0, 15, " three hungry") + new Node(EXPRESSION_NODE, 0, 29, + new Node(ALTERNATION_NODE, 0,29, + new Node(ALTERNATIVE_NODE,0,15, + new Node(TEXT_NODE, 0, 15, " three hungry") ), - new AstNode(ALTERNATIVE_NODE,16,29, - new AstNode(TEXT_NODE, 16, 29, "blind mice ") + new Node(ALTERNATIVE_NODE,16,29, + new Node(TEXT_NODE, 16, 29, "blind mice ") ) ) @@ -331,16 +330,16 @@ void alternationWithWhiteSpace() { @Test void alternationWithUnusedEndOptional() { assertThat(astOf("three )blind\\ mice/rats"), equalTo( - new AstNode(EXPRESSION_NODE,0,23, - new AstNode(TEXT_NODE, 0, 5, "three"), - new AstNode(TEXT_NODE, 5, 6, " "), - new AstNode(ALTERNATION_NODE,6,23, - new AstNode(ALTERNATIVE_NODE,6,18, - new AstNode(TEXT_NODE, 6, 7, ")"), - new AstNode(TEXT_NODE, 7, 18, "blind mice") + new Node(EXPRESSION_NODE,0,23, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(ALTERNATION_NODE,6,23, + new Node(ALTERNATIVE_NODE,6,18, + new Node(TEXT_NODE, 6, 7, ")"), + new Node(TEXT_NODE, 7, 18, "blind mice") ), - new AstNode(ALTERNATIVE_NODE,19,23, - new AstNode(TEXT_NODE, 19, 23, "rats") + new Node(ALTERNATIVE_NODE,19,23, + new Node(TEXT_NODE, 19, 23, "rats") ) ) ) @@ -364,17 +363,17 @@ void alternationWithUnusedStartOptional() { @Test void alternationFollowedByOptional() { assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( - new AstNode(EXPRESSION_NODE,0,23, - new AstNode(TEXT_NODE, 0, 5, "three"), - new AstNode(TEXT_NODE, 5, 6, " "), - new AstNode(ALTERNATION_NODE,6,23, - new AstNode(ALTERNATIVE_NODE,6,16, - new AstNode(TEXT_NODE, 6, 16, "blind rat") + new Node(EXPRESSION_NODE,0,23, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(ALTERNATION_NODE,6,23, + new Node(ALTERNATIVE_NODE,6,16, + new Node(TEXT_NODE, 6, 16, "blind rat") ), - new AstNode(ALTERNATIVE_NODE,17,23, - new AstNode(TEXT_NODE, 17, 20, "cat"), - new AstNode(OPTIONAL_NODE,20,23, - new AstNode(TEXT_NODE, 21, 22, "s") + new Node(ALTERNATIVE_NODE,17,23, + new Node(TEXT_NODE, 17, 20, "cat"), + new Node(OPTIONAL_NODE,20,23, + new Node(TEXT_NODE, 21, 22, "s") ) ) ) @@ -382,7 +381,7 @@ void alternationFollowedByOptional() { )); } - private AstNode astOf(String expression) { + private Node astOf(String expression) { return parser.parse(expression); } From 2258c5b1d6bce493275a678d2804f1ca22325597 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 17 Jul 2020 17:55:09 +0200 Subject: [PATCH 081/183] Structure and naming --- .../cucumberexpressions/CucumberExpressionTokenizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 922ee3b3f2..284d01d671 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -35,7 +35,7 @@ private static class TokenIterator implements Iterator { int index; int escaped; - public TokenIterator(String expression) { + TokenIterator(String expression) { this.expression = expression; codePoints = expression.codePoints().iterator(); buffer = new StringBuilder(); From baf8616fa2522a7366cc13001c4a7b88ef8c7a15 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 17 Jul 2020 18:03:43 +0200 Subject: [PATCH 082/183] Structure and naming --- .../io/cucumber/cucumberexpressions/Ast.java | 65 +++++++++++++++---- .../CucumberExpressionTokenizer.java | 39 ++--------- 2 files changed, 59 insertions(+), 45 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 441826d51b..e7e7d0eec5 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -10,16 +10,18 @@ final class Ast { - static final char ESCAPE_CHARACTER = '\\'; - static final char ALTERNATION_CHARACTER = '/'; - static final char BEGIN_PARAMETER_CHARACTER = '{'; - static final char END_PARAMETER_CHARACTER = '}'; - static final char BEGIN_OPTIONAL_CHARACTER = '('; - static final char END_OPTIONAL_CHARACTER = ')'; + private static final char ESCAPE_CHARACTER = '\\'; + private static final char ALTERNATION_CHARACTER = '/'; + private static final char BEGIN_PARAMETER_CHARACTER = '{'; + private static final char END_PARAMETER_CHARACTER = '}'; + private static final char BEGIN_OPTIONAL_CHARACTER = '('; + private static final char END_OPTIONAL_CHARACTER = ')'; interface Located { int start(); + int end(); + } static final class Node implements Located { @@ -60,10 +62,11 @@ enum Type { EXPRESSION_NODE } - public int start(){ + public int start() { return startIndex; } - public int end(){ + + public int end() { return endIndex; } @@ -154,10 +157,50 @@ static final class Token implements Located { this.endIndex = endIndex; } - public int start(){ + static boolean canEscape(Integer token) { + if (Character.isWhitespace(token)) { + return true; + } + switch (token) { + case (int) ESCAPE_CHARACTER: + case (int) ALTERNATION_CHARACTER: + case (int) BEGIN_PARAMETER_CHARACTER: + case (int) END_PARAMETER_CHARACTER: + case (int) BEGIN_OPTIONAL_CHARACTER: + case (int) END_OPTIONAL_CHARACTER: + return true; + } + return false; + } + + static Type typeOf(Integer token) { + if (Character.isWhitespace(token)) { + return Type.WHITE_SPACE; + } + switch (token) { + case (int) ALTERNATION_CHARACTER: + return Type.ALTERNATION; + case (int) BEGIN_PARAMETER_CHARACTER: + return Type.BEGIN_PARAMETER; + case (int) END_PARAMETER_CHARACTER: + return Type.END_PARAMETER; + case (int) BEGIN_OPTIONAL_CHARACTER: + return Type.BEGIN_OPTIONAL; + case (int) END_OPTIONAL_CHARACTER: + return Type.END_OPTIONAL; + } + return Type.TEXT; + } + + static boolean isEscapeCharacter(int token) { + return token == ESCAPE_CHARACTER; + } + + public int start() { return startIndex; } - public int end(){ + + public int end() { return endIndex; } @@ -193,7 +236,7 @@ enum Type { START_OF_LINE, END_OF_LINE, WHITE_SPACE, - BEGIN_OPTIONAL(""+ BEGIN_OPTIONAL_CHARACTER, "optional text"), + BEGIN_OPTIONAL("" + BEGIN_OPTIONAL_CHARACTER, "optional text"), END_OPTIONAL("" + END_OPTIONAL_CHARACTER, "optional text"), BEGIN_PARAMETER("" + BEGIN_PARAMETER_CHARACTER, "a parameter"), END_PARAMETER("" + END_PARAMETER_CHARACTER, "a parameter"), diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 284d01d671..2de1541948 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -64,7 +64,7 @@ public Token next() { while (codePoints.hasNext()) { int token = codePoints.nextInt(); - if (!treatAsText && token == Ast.ESCAPE_CHARACTER) { + if (!treatAsText && Token.isEscapeCharacter(token)) { escaped++; treatAsText = true; continue; @@ -119,44 +119,15 @@ private Token convertBufferToToken(Type currentTokenType) { } private Type tokenTypeOf(Integer token, boolean treatAsText) { - return treatAsText ? textTokenTypeOf(token) : tokenTypeOf(token); - } - - private Type textTokenTypeOf(Integer token) { - if (Character.isWhitespace(token)) { - return Type.TEXT; + if (!treatAsText) { + return Token.typeOf(token); } - switch (token) { - case (int) Ast.ESCAPE_CHARACTER: - case (int) Ast.ALTERNATION_CHARACTER: - case (int) Ast.BEGIN_PARAMETER_CHARACTER: - case (int) Ast.END_PARAMETER_CHARACTER: - case (int) Ast.BEGIN_OPTIONAL_CHARACTER: - case (int) Ast.END_OPTIONAL_CHARACTER: - return Type.TEXT; + if (Token.canEscape(token)) { + return Type.TEXT; } throw createCantEscape(expression, index + escaped); } - private Type tokenTypeOf(Integer token) { - if (Character.isWhitespace(token)) { - return Type.WHITE_SPACE; - } - switch (token) { - case (int) Ast.ALTERNATION_CHARACTER: - return Type.ALTERNATION; - case (int) Ast.BEGIN_PARAMETER_CHARACTER: - return Type.BEGIN_PARAMETER; - case (int) Ast.END_PARAMETER_CHARACTER: - return Type.END_PARAMETER; - case (int) Ast.BEGIN_OPTIONAL_CHARACTER: - return Type.BEGIN_OPTIONAL; - case (int) Ast.END_OPTIONAL_CHARACTER: - return Type.END_OPTIONAL; - } - return Type.TEXT; - } - } } From 070ffa7ba1791025934e5cea7303b5350bb1c074 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 17 Jul 2020 18:09:19 +0200 Subject: [PATCH 083/183] Structure and naming --- .../CucumberExpressionTokenizer.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index 2de1541948..e38f28bfc4 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -26,24 +26,20 @@ private Iterable tokenizeImpl(String expression) { } private static class TokenIterator implements Iterator { - final OfInt codePoints; + private final String expression; - StringBuilder buffer; - Type previousTokenType; - Type currentTokenType; - boolean treatAsText; - int index; - int escaped; + private final OfInt codePoints; + + private StringBuilder buffer = new StringBuilder(); + private Type previousTokenType; + private Type currentTokenType = Type.START_OF_LINE; + private boolean treatAsText; + private int index; + private int escaped; TokenIterator(String expression) { this.expression = expression; - codePoints = expression.codePoints().iterator(); - buffer = new StringBuilder(); - previousTokenType = null; - currentTokenType = Type.START_OF_LINE; - treatAsText = false; - index = 0; - escaped = 0; + this.codePoints = expression.codePoints().iterator(); } @Override From a3a1d8dab96658ca0aa075f859567b81b886ab51 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 17 Jul 2020 18:22:08 +0200 Subject: [PATCH 084/183] Structure and naming --- .../CucumberExpressionParser.java | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 0d5110efb1..725f8c820d 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -220,36 +220,41 @@ private static boolean lookingAt(List tokens, int at, Type token) { return tokens.get(at).type == token; } - private static List splitAlternatives(int start, int end, List node) { - - List seperators = new ArrayList<>(); + private static List splitAlternatives(int start, int end, List alternation) { + List separators = new ArrayList<>(); List> alternatives = new ArrayList<>(); List alternative = new ArrayList<>(); - for (Node token : node) { - if (ALTERNATIVE_NODE.equals(token.type())) { - seperators.add(token); + for (Node node : alternation) { + if (ALTERNATIVE_NODE.equals(node.type())) { + separators.add(node); alternatives.add(alternative); alternative = new ArrayList<>(); } else { - alternative.add(token); + alternative.add(node); } } alternatives.add(alternative); - List alts = new ArrayList<>(); + return createAlternativeNodes(start, end, separators, alternatives); + } + + private static List createAlternativeNodes(int start, int end, List separators, List> alternatives) { + List nodes = new ArrayList<>(); for (int i = 0; i < alternatives.size(); i++) { - List nodes = alternatives.get(i); + List node = alternatives.get(i); if (i == 0) { - alts.add(new Node(ALTERNATIVE_NODE, start, seperators.get(i).start(), nodes)); + Node rightSeparator = separators.get(i); + nodes.add(new Node(ALTERNATIVE_NODE, start, rightSeparator.start(), node)); } else if (i == alternatives.size() - 1) { - alts.add(new Node(ALTERNATIVE_NODE, seperators.get(i - 1).end(), end, nodes)); + Node leftSeparator = separators.get(i - 1); + nodes.add(new Node(ALTERNATIVE_NODE, leftSeparator.end(), end, node)); } else { - alts.add(new Node(ALTERNATIVE_NODE, seperators.get(i - 1).end(), seperators.get(i).start(), - nodes)); + Node leftSeparator = separators.get(i - 1); + Node rightSeparator = separators.get(i); + nodes.add(new Node(ALTERNATIVE_NODE, leftSeparator.end(), rightSeparator.start(), node)); } } - - return alts; + return nodes; } } From 49271a1820dfb7c10fdd8a8cdc57ceb7f9d4b079 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 17 Jul 2020 18:22:58 +0200 Subject: [PATCH 085/183] Structure and naming --- .../cucumberexpressions/CucumberExpressionTokenizer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index e38f28bfc4..e8d89380cc 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -22,7 +22,6 @@ List tokenize(String expression) { private Iterable tokenizeImpl(String expression) { return () -> new TokenIterator(expression); - } private static class TokenIterator implements Iterator { From 98b84b0badb7f23a04e5460511dabd480334937a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 24 Jul 2020 22:14:26 +0200 Subject: [PATCH 086/183] WIP --- cucumber-expressions/go/ast.go | 45 +- .../go/cucumber_expression.go | 20 +- .../go/cucumber_expression_parser.go | 189 ++++---- .../go/cucumber_expression_parser_test.go | 439 +++++++++--------- .../go/cucumber_expression_tokenizer.go | 75 +-- .../io/cucumber/cucumberexpressions/Ast.java | 110 +++-- .../CucumberExpressionParser.java | 25 +- 7 files changed, 415 insertions(+), 488 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index d0ead6fe9b..369ac13d40 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -2,6 +2,13 @@ package cucumberexpressions import "strings" +const escapeCharacter = '\\' +const alternationCharacter = '/' +const beginParameterCharacter = '{' +const endParameterCharacter = '}' +const beginOptionalCharacter = '(' +const endOptionalCharacter = ')' + type nodeType int const ( @@ -13,46 +20,34 @@ const ( expressionNode ) -type astNode struct { +type node struct { nodeType nodeType - nodes []astNode - token token + start int + end int + token string + nodes []node } -func (node astNode) text() string { +func (node node) text() string { builder := strings.Builder{} - builder.WriteString(node.token.text) + builder.WriteString(node.token) for _, c := range node.nodes { builder.WriteString(c.text()) } return builder.String() } -// Marker. This way we don't need to model the -// the tail end of alternation in the AST: -// -// alternation := alternative* + ( '/' + alternative* )+ -var alternativeSeparator = astNode{alternativeNode, []astNode{}, token{"/", alternation}} - type tokenType int const ( startOfLine tokenType = iota endOfLine - // In order of precedence - whiteSpaceEscaped whiteSpace - beginOptionalEscaped beginOptional - endOptionalEscaped endOptional - beginParameterEscaped beginParameter - endParameterEscaped endParameter - alternationEscaped alternation - escapeEscaped escape text ) @@ -60,14 +55,8 @@ const ( type token struct { text string tokenType tokenType + start int + end int } -var beginOptionalToken = token{"(", beginOptional} -var endOptionalToken = token{")", endOptional} -var beginParameterToken = token{"{", beginParameter} -var endParameterToken = token{"}", endParameter} -var alternationToken = token{"/", alternation} -var escapeToken = token{"\\", escape} - -var nullNode = astNode{textNode, []astNode{}, nullToken} -var nullToken = token{"", text} +var nullNode = node{textNode, -1, -1, "", []node{}} diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 4455831f62..7c6e567f17 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -39,10 +39,10 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return result, nil } -func (c *CucumberExpression) rewriteNodeToRegex(node astNode) (string, error) { +func (c *CucumberExpression) rewriteNodeToRegex(node node) (string, error) { switch node.nodeType { case textNode: - return c.processEscapes(node.token.text), nil + return c.processEscapes(node.token), nil case optionalNode: return c.rewriteOptional(node) case alternationNode: @@ -62,7 +62,7 @@ func (c *CucumberExpression) processEscapes(expression string) string { return escapeRegexp.ReplaceAllString(expression, `\$1`) } -func (c *CucumberExpression) rewriteOptional(node astNode) (string, error) { +func (c *CucumberExpression) rewriteOptional(node node) (string, error) { err := c.assertNoParameters(node, parameterTypesCanNotBeOptional) if err != nil { return "", err @@ -74,7 +74,7 @@ func (c *CucumberExpression) rewriteOptional(node astNode) (string, error) { return c.rewriteNodesToRegex(node.nodes, "", "(?:", ")?") } -func (c *CucumberExpression) rewriteAlternation(node astNode) (string, error) { +func (c *CucumberExpression) rewriteAlternation(node node) (string, error) { // Make sure the alternative parts aren't empty and don't contain parameter types for _, alternative := range node.nodes { if len(alternative.nodes) == 0 { @@ -92,11 +92,11 @@ func (c *CucumberExpression) rewriteAlternation(node astNode) (string, error) { return c.rewriteNodesToRegex(node.nodes, "|", "(?:", ")") } -func (c *CucumberExpression) rewriteAlternative(node astNode) (string, error) { +func (c *CucumberExpression) rewriteAlternative(node node) (string, error) { return c.rewriteNodesToRegex(node.nodes, "", "", "") } -func (c *CucumberExpression) rewriteParameter(node astNode) (string, error) { +func (c *CucumberExpression) rewriteParameter(node node) (string, error) { buildCaptureRegexp := func(regexps []*regexp.Regexp) string { if len(regexps) == 1 { return fmt.Sprintf("(%s)", regexps[0].String()) @@ -124,11 +124,11 @@ func (c *CucumberExpression) rewriteParameter(node astNode) (string, error) { return buildCaptureRegexp(parameterType.regexps), nil } -func (c *CucumberExpression) rewriteExpression(node astNode) (string, error) { +func (c *CucumberExpression) rewriteExpression(node node) (string, error) { return c.rewriteNodesToRegex(node.nodes, "", "^", "$") } -func (c *CucumberExpression) rewriteNodesToRegex(nodes []astNode, delimiter string, prefix string, suffix string) (string, error) { +func (c *CucumberExpression) rewriteNodesToRegex(nodes []node, delimiter string, prefix string, suffix string) (string, error) { builder := strings.Builder{} builder.WriteString(prefix) for i, node := range nodes { @@ -145,7 +145,7 @@ func (c *CucumberExpression) rewriteNodesToRegex(nodes []astNode, delimiter stri return builder.String(), nil } -func (c *CucumberExpression) assertNotEmpty(node astNode, message string) error { +func (c *CucumberExpression) assertNotEmpty(node node, message string) error { for _, node := range node.nodes { if node.nodeType == textNode { return nil @@ -154,7 +154,7 @@ func (c *CucumberExpression) assertNotEmpty(node astNode, message string) error return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) } -func (c *CucumberExpression) assertNoParameters(node astNode, message string) error { +func (c *CucumberExpression) assertNoParameters(node node, message string) error { for _, node := range node.nodes { if node.nodeType == parameterNode { return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 154c6dbfff..53703a1362 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -1,33 +1,11 @@ package cucumberexpressions -type parser func(expression []token, current int) (int, astNode) - /* - * parameter := '{' + text* + '}' + * text := token */ -var textParser = func(expression []token, current int) (int, astNode) { - unEscape := func(t token) token { - switch t.tokenType { - case whiteSpaceEscaped: - return token{t.text[1:], whiteSpace} - case beginOptionalEscaped: - return beginOptionalToken - case endOptionalEscaped: - return endOptionalToken - case beginParameterEscaped: - return beginParameterToken - case endParameterEscaped: - return endParameterToken - case alternationEscaped: - return alternationToken - case escapeEscaped: - return escapeToken - default: - return t - } - } - currentToken := expression[current] - return 1, astNode{textNode, []astNode{}, unEscape(currentToken)} +var textParser = func(tokens []token, current int) (int, node) { + token := tokens[current] + return 1, node{textNode, token.start, token.end, token.text, []node{}} } /* @@ -52,30 +30,13 @@ var optionalParser = parseBetween( textParser, ) -func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, parsers ...parser) parser { - return func(expression []token, current int) (int, astNode) { - if !lookingAt(expression, current, beginToken) { - return 0, nullNode - } - - subCurrent := current + 1 - consumed, subAst := parseTokensUntil(parsers, expression, subCurrent, endToken) - subCurrent += consumed - - // endToken not found - if !lookingAt(expression, subCurrent, endToken) { - return 0, nullNode - } - // consumes endToken - return subCurrent + 1 - current, astNode{nodeType, subAst, nullToken} - } -} - -var alternativeSeparatorParser = func(expression []token, current int) (int, astNode) { - if !lookingAt(expression, current, alternation) { +// alternation := alternative* + ( '/' + alternative* )+ +var alternativeSeparatorParser = func(tokens []token, current int) (int, node) { + if !lookingAt(tokens, current, alternation) { return 0, nullNode } - return 1, alternativeSeparator + token := tokens[current] + return 1, node{alternativeNode, token.start, token.end, token.text, []node{}} } var alternativeParsers = []parser{ @@ -90,71 +51,83 @@ var alternativeParsers = []parser{ * boundary := whitespace | ^ | $ * alternative: = optional | parameter | text */ -var alternationParser = func(expression []token, current int) (int, astNode) { +var alternationParser = func(tokens []token, current int) (int, node) { previous := current - 1 - if !lookingAtAny(expression, previous, startOfLine, whiteSpace) { + if !lookingAtAny(tokens, previous, startOfLine, whiteSpace) { return 0, nullNode } - consumed, subAst := parseTokensUntil(alternativeParsers, expression, current, whiteSpace, endOfLine) - var contains = func(s []astNode, node astNode) bool { + consumed, subAst := parseTokensUntil(alternativeParsers, tokens, current, whiteSpace, endOfLine) + var contains = func(s []node, nodeType nodeType) bool { for _, a := range s { - if a.nodeType == node.nodeType { + if a.nodeType == nodeType { return true } } return false } - - if !contains(subAst, alternativeSeparator) { + subCurrent := current + consumed + if !contains(subAst, alternativeNode) { return 0, nullNode } - splitAlternatives := func(subAst []astNode) []astNode { - alternatives := make([]astNode, 0) - alternative := make([]astNode, 0) - for _, node := range subAst { - if node.nodeType == alternativeSeparator.nodeType { - alternatives = append(alternatives, astNode{alternativeNode, alternative, nullToken}) - alternative = make([]astNode, 0) - } else { - alternative = append(alternative, node) - } - } - return append(alternatives, astNode{alternativeNode, alternative, nullToken}) - } - // Does not consume right hand boundary token - return consumed, astNode{alternationNode, splitAlternatives(subAst), nullToken} - + start := tokens[current].start + end := tokens[subCurrent].end + return consumed, node{alternationNode, start, end, "", splitAlternatives(start, end, subAst)} } -var cucumberExpressionParsers = []parser{ +/* + * cucumber-expression := ( alternation | optional | parameter | text )* + */ +var cucumberExpressionParser = parseBetween( + expressionNode, + startOfLine, + endOfLine, alternationParser, optionalParser, parameterParser, textParser, -} +) -/* - * cucumber-expression := ( alternation | optional | parameter | text )* - */ -func parse(expression string) (astNode, error) { +func parse(expression string) (node, error) { tokens, err := tokenize(expression) if err != nil { return nullNode, err } - consumed, ast := parseTokensUntil(cucumberExpressionParsers, tokens, 0, endOfLine) + consumed, ast := cucumberExpressionParser(tokens, 0) if consumed != len(tokens) { // Can't happen if configured properly return nullNode, NewCucumberExpressionError("Could not parse" + expression) } + return ast, nil +} + +type parser func(tokens []token, current int) (int, node) + +func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, parsers ...parser) parser { + return func(tokens []token, current int) (int, node) { + if !lookingAt(tokens, current, beginToken) { + return 0, nullNode + } - return astNode{expressionNode, ast, nullToken}, nil + subCurrent := current + 1 + consumed, subAst := parseTokensUntil(parsers, tokens, subCurrent, endToken) + subCurrent += consumed + + // endToken not found + if !lookingAt(tokens, subCurrent, endToken) { + return 0, nullNode + } + // consumes endToken + start := tokens[current].start + end := tokens[subCurrent].end + return subCurrent + 1 - current, node{nodeType, start, end, "", subAst} + } } -func parseTokensUntil(parsers []parser, expresion []token, startAt int, endTokens ...tokenType) (int, []astNode) { - ast := make([]astNode, 0) +func parseTokensUntil(parsers []parser, expresion []token, startAt int, endTokens ...tokenType) (int, []node) { + ast := make([]node, 0) current := startAt size := len(expresion) for current < size { @@ -174,7 +147,7 @@ func parseTokensUntil(parsers []parser, expresion []token, startAt int, endToken return current - startAt, ast } -func parseToken(parsers []parser, expresion []token, startAt int) (int, astNode) { +func parseToken(parsers []parser, expresion []token, startAt int) (int, node) { for _, parser := range parsers { consumed, node := parser(expresion, startAt) if consumed != 0 { @@ -185,22 +158,58 @@ func parseToken(parsers []parser, expresion []token, startAt int) (int, astNode) return 0, nullNode } -func lookingAtAny(expression []token, at int, tokens ...tokenType) bool { - for _, token := range tokens { - if lookingAt(expression, at, token) { +func lookingAtAny(tokens []token, at int, tokenTypes ...tokenType) bool { + for _, tokenType := range tokenTypes { + if lookingAt(tokens, at, tokenType) { return true } } return false } -func lookingAt(expression []token, at int, token tokenType) bool { - size := len(expression) +func lookingAt(tokens []token, at int, tokenType tokenType) bool { + size := len(tokens) if at < 0 { - return token == startOfLine + return tokenType == startOfLine } if at >= size { - return token == endOfLine + return tokenType == endOfLine + } + return tokens[at].tokenType == tokenType +} + +func splitAlternatives(start int, end int, alternation []node) []node { + separators := make([]node, 0) + alternatives := make([][]node, 0) + alternative := make([]node, 0) + for _, n := range alternation { + if n.nodeType == alternativeNode { + separators = append(separators, n) + alternatives = append(alternatives, alternative) + alternative = make([]node, 0) + } else { + alternative = append(alternative, n) + } + } + alternatives = append(alternatives, alternative) + + return createAlternativeNodes(start, end, separators, alternatives) +} + +func createAlternativeNodes(start int, end int, separators []node, alternatives [][]node) []node { + nodes := make([]node, 0) + for i, n := range alternatives { + if i == 0 { + rightSeparator := separators[i] + nodes = append(nodes, node{alternativeNode, start, rightSeparator.start, "", n}) + } else if i == len(alternatives)-1 { + leftSeparator := separators[i-1] + nodes = append(nodes, node{alternativeNode, leftSeparator.end, end, "", n}) + } else { + rightSeparator := separators[i-1] + leftSeparator := separators[i] + nodes = append(nodes, node{alternativeNode, leftSeparator.end, rightSeparator.start, "", n}) + } } - return expression[at].tokenType == token + return nodes } diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index 34c364b27a..66ddf189d6 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -6,338 +6,343 @@ import ( ) func TestCucumberExpressionParser(t *testing.T) { - var assertAst = func(t *testing.T, expression string, expected astNode) { + var assertAst = func(t *testing.T, expression string, expected node) { ast, err := parse(expression) require.NoError(t, err) require.Equal(t, expected, ast) } t.Run("empty string", func(t *testing.T) { - assertAst(t, "", astNode{ + assertAst(t, "", node{ expressionNode, - []astNode{}, - nullToken, + -1, -1, + "", + []node{}, }) }) t.Run("phrase", func(t *testing.T) { - assertAst(t, "three blind mice", astNode{ + assertAst(t, "three blind mice", node{ expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"blind", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, + -1, -1, + "", + []node{ + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "blind", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, }, - nullToken, }) }) t.Run("optional", func(t *testing.T) { - assertAst(t, "(blind)", astNode{ + assertAst(t, "(blind)", node{ expressionNode, - []astNode{ + -1, -1, + "", + []node{ {optionalNode, - []astNode{ - {textNode, []astNode{}, token{"blind", text}}, + -1, + -1, + "", + []node{ + {textNode, -1, -1, "blind", []node{}}, }, - nullToken, }, }, - nullToken, }) }) t.Run("parameter", func(t *testing.T) { - assertAst(t, "{string}", astNode{ - expressionNode, - []astNode{ - {parameterNode, - []astNode{ - {textNode, []astNode{}, token{"string", text}}, + assertAst(t, "{string}", node{ + expressionNode, -1, -1, + "", + []node{ + {parameterNode, -1, -1, + "", + []node{ + {textNode, -1, -1, "string", []node{}}, }, - nullToken, }, }, - nullToken, }) }) t.Run("anonymous parameter", func(t *testing.T) { - assertAst(t, "{}", astNode{ - expressionNode, - []astNode{ - {parameterNode, - []astNode{}, - nullToken, + assertAst(t, "{}", node{ + expressionNode, -1, -1, + []node{ + {parameterNode, -1, -1, + []node{}, + "", }, }, - nullToken, + "", }) }) t.Run("optional phrase", func(t *testing.T) { - assertAst(t, "three (blind) mice", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {optionalNode, []astNode{ - {textNode, []astNode{}, token{"blind", text}}, - }, nullToken}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, + assertAst(t, "three (blind) mice", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {optionalNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "blind"}, + }, ""}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "mice"}, }, - nullToken, + "", }) }) t.Run("slash", func(t *testing.T) { - assertAst(t, "\\", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"\\", escape}}, + assertAst(t, "\\", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "\\"}, }, - nullToken, + "", }) }) t.Run("opening brace", func(t *testing.T) { - assertAst(t, "{", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"{", beginParameter}}, + assertAst(t, "{", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "{"}, }, - nullToken, + "", }) }) t.Run("unfinished parameter", func(t *testing.T) { - assertAst(t, "{string", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"{", beginParameter}}, - {textNode, []astNode{}, token{"string", text}}, + assertAst(t, "{string", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "{"}, + {textNode, -1, -1 []node{}, "string"}, }, - nullToken, + "", }) }) t.Run("opening parenthesis", func(t *testing.T) { - assertAst(t, "(", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"(", beginOptional}}, + assertAst(t, "(", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "("}, }, - nullToken, + "", }) }) t.Run("escaped opening parenthesis", func(t *testing.T) { - assertAst(t, "\\(", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"(", beginOptional}}, + assertAst(t, "\\(", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "("}, }, - nullToken, + "", }) }) t.Run("escaped optional phrase", func(t *testing.T) { - assertAst(t, "three \\(blind) mice", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"(", beginOptional}}, - {textNode, []astNode{}, token{"blind", text}}, - {textNode, []astNode{}, token{")", endOptional}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, + assertAst(t, "three \\(blind) mice", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "("}, + {textNode, -1, -1 []node{}, "blind"}, + {textNode, -1, -1 []node{}, ")"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "mice"}, }, - nullToken, + "", }) }) t.Run("escaped optional followed by optional", func(t *testing.T) { - assertAst(t, "three \\((very) blind) mice", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"(", beginOptional}}, - {optionalNode, []astNode{ - {textNode, []astNode{}, token{"very", text}}, - }, nullToken}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"blind", text}}, - {textNode, []astNode{}, token{")", endOptional}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, + assertAst(t, "three \\((very) blind) mice", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "("}, + {optionalNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "very"}, + }, ""}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "blind"}, + {textNode, -1, -1 []node{}, ")"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "mice"}, }, - nullToken, + "", }) }) t.Run("optional containing escaped optional", func(t *testing.T) { - assertAst(t, "three ((very\\) blind) mice", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {optionalNode, []astNode{ - {textNode, []astNode{}, token{"(", beginOptional}}, - {textNode, []astNode{}, token{"very", text}}, - {textNode, []astNode{}, token{")", endOptional}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"blind", text}}, - }, nullToken}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, + assertAst(t, "three ((very\\) blind) mice", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {optionalNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "("}, + {textNode, -1, -1 []node{}, "very"}, + {textNode, -1, -1 []node{}, ")"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "blind"}, + }, ""}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "mice"}, }, - nullToken, + "", }) }) t.Run("alternation", func(t *testing.T) { - assertAst(t, "mice/rats", astNode{ - expressionNode, - []astNode{ - {alternationNode, []astNode{ - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"mice", text}}, - }, nullToken}, - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"rats", text}}, - }, nullToken}, - }, nullToken}, + assertAst(t, "mice/rats", node{ + expressionNode, -1, -1, + []node{ + {alternationNode, -1, -1 []node{ + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "mice"}, + }, ""}, + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "rats"}, + }, ""}, + }, ""}, }, - nullToken, + "", }) }) t.Run("escaped alternation", func(t *testing.T) { - assertAst(t, "mice\\/rats", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"mice", text}}, - {textNode, []astNode{}, token{"/", alternation}}, - {textNode, []astNode{}, token{"rats", text}}, + assertAst(t, "mice\\/rats", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "mice"}, + {textNode, -1, -1 []node{}, "/"}, + {textNode, -1, -1 []node{}, "rats"}, }, - nullToken, + "", }) }) t.Run("alternation phrase", func(t *testing.T) { - assertAst(t, "three hungry/blind mice", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {alternationNode, []astNode{ - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"hungry", text}}, - }, nullToken}, - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"blind", text}}, - }, nullToken}, - }, nullToken}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, + assertAst(t, "three hungry/blind mice", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {alternationNode, -1, -1 []node{ + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "hungry"}, + }, ""}, + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "blind"}, + }, ""}, + }, ""}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "mice"}, }, - nullToken, + "", }) }) t.Run("alternation with whitespace", func(t *testing.T) { - assertAst(t, "\\ three\\ hungry/blind\\ mice\\ ", astNode{ - expressionNode, - []astNode{ - {alternationNode, []astNode{ - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"hungry", text}}, - }, nullToken}, - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"blind", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - }, nullToken}, - }, nullToken}, + assertAst(t, "\\ three\\ hungry/blind\\ mice\\ ", node{ + expressionNode, -1, -1, + []node{ + {alternationNode, -1, -1 []node{ + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "hungry"}, + }, ""}, + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "blind"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "mice"}, + {textNode, -1, -1 []node{}, " "}, + }, ""}, + }, ""}, }, - nullToken, + "", }) }) t.Run("alternation with unused end optional", func(t *testing.T) { - assertAst(t, "three )blind\\ mice/rats", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {alternationNode, []astNode{ - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{")", endOptional}}, - {textNode, []astNode{}, token{"blind", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, - }, nullToken}, - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"rats", text}}, - }, nullToken}, - }, nullToken}, + assertAst(t, "three )blind\\ mice/rats", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {alternationNode, -1, -1 []node{ + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, ")"}, + {textNode, -1, -1 []node{}, "blind"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "mice"}, + }, ""}, + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "rats"}, + }, ""}, + }, ""}, }, - nullToken, + "", }) }) t.Run("alternation with unused start optional", func(t *testing.T) { - assertAst(t, "three blind\\ mice/rats(", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {alternationNode, []astNode{ - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"blind", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"mice", text}}, - }, nullToken}, - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"rats", text}}, - {textNode, []astNode{}, token{"(", beginOptional}}, - }, nullToken}, - }, nullToken}, + assertAst(t, "three blind\\ mice/rats(", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {alternationNode, -1, -1 []node{ + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "blind"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "mice"}, + }, ""}, + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "rats"}, + {textNode, -1, -1 []node{}, "("}, + }, ""}, + }, ""}, }, - nullToken, + "", }) }) t.Run("alternation with unused start optional", func(t *testing.T) { - assertAst(t, "three blind\\ rat/cat(s)", astNode{ - expressionNode, - []astNode{ - {textNode, []astNode{}, token{"three", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {alternationNode, []astNode{ - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"blind", text}}, - {textNode, []astNode{}, token{" ", whiteSpace}}, - {textNode, []astNode{}, token{"rat", text}}, - }, nullToken}, - {alternativeNode, []astNode{ - {textNode, []astNode{}, token{"cat", text}}, - {optionalNode, []astNode{ - {textNode, []astNode{}, token{"s", text}}, - }, nullToken}, - }, nullToken}, - }, nullToken}, + assertAst(t, "three blind\\ rat/cat(s)", node{ + expressionNode, -1, -1, + []node{ + {textNode, -1, -1 []node{}, "three"}, + {textNode, -1, -1 []node{}, " "}, + {alternationNode, -1, -1 []node{ + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "blind"}, + {textNode, -1, -1 []node{}, " "}, + {textNode, -1, -1 []node{}, "rat"}, + }, ""}, + {alternativeNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "cat"}, + {optionalNode, -1, -1 []node{ + {textNode, -1, -1 []node{}, "s"}, + }, ""}, + }, ""}, + }, ""}, }, - nullToken, + "", }) }) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 72d79b5f65..6285edad7f 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -7,79 +7,6 @@ import ( type tokenizer func(expression string, current int) (int, token) -var tokenizers = []tokenizer{ - tokenizePattern(whiteSpaceEscaped, regexp.MustCompile(`\\\s`)), - tokenizePattern(whiteSpace, regexp.MustCompile(`\s+`)), - - tokenizeString(beginOptionalEscaped, "\\("), - tokenizeString(beginOptional, "("), - - tokenizeString(endOptionalEscaped, "\\)"), - tokenizeString(endOptional, ")"), - - tokenizeString(beginParameterEscaped, "\\{"), - tokenizeString(beginParameter, "{"), - - tokenizeString(endParameterEscaped, "\\}"), - tokenizeString(endParameter, "}"), - - tokenizeString(alternationEscaped, "\\/"), - tokenizeString(alternation, "/"), - - tokenizeString(escapeEscaped, "\\\\"), - tokenizeString(escape, "\\"), - - // Should be `.` but this creates a nicer parse tree. - tokenizePattern(text, regexp.MustCompile(`[^(){}\\/\s]+`)), -} - -/* - * token := '\' + whitespace | whitespace | '\(' | '(' | '\)' | ')' | - * '\{' | '{' | '\}' | '}' | '\/' | '/' | '\\' | '\' | . - */ func tokenize(expression string) ([]token, error) { - length := len(expression) - tokens := make([]token, 0) - - current := 0 - for current < length { - tokenized := false - for _, tokenizer := range tokenizers { - consumed, token := tokenizer(expression, current) - if consumed != 0 { - tokens = append(tokens, token) - current += consumed - tokenized = true - break - } - } - if !tokenized { - // Can't happen if configured properly - // Leave in to avoid looping if not configured properly - return tokens, NewCucumberExpressionError("Could not tokenize" + expression) - } - } - - return tokens, nil -} - -func tokenizeString(tokenType tokenType, pattern string) tokenizer { - return func(expression string, current int) (int, token) { - if !strings.HasPrefix(expression[current:], pattern) { - return 0, token{"", tokenType} - } - return len(pattern), token{pattern, tokenType} - } -} - -func tokenizePattern(tokenType tokenType, regexp *regexp.Regexp) tokenizer { - return func(expression string, current int) (int, token) { - tail := expression[current:] - loc := regexp.FindStringIndex(tail) - if loc == nil || loc[0] != 0 { - return 0, token{"", tokenType} - } - match := tail[0:loc[1]] - return loc[1], token{match, tokenType} - } + return []token{}, nil } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index e7e7d0eec5..749736871f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -10,12 +10,12 @@ final class Ast { - private static final char ESCAPE_CHARACTER = '\\'; - private static final char ALTERNATION_CHARACTER = '/'; - private static final char BEGIN_PARAMETER_CHARACTER = '{'; - private static final char END_PARAMETER_CHARACTER = '}'; - private static final char BEGIN_OPTIONAL_CHARACTER = '('; - private static final char END_OPTIONAL_CHARACTER = ')'; + private static final char escapeCharacter = '\\'; + private static final char alternationCharacter = '/'; + private static final char beginParameterCharacter = '{'; + private static final char endParameterCharacter = '}'; + private static final char beginOptionalCharacter = '('; + private static final char endOptionalCharacter = ')'; interface Located { int start(); @@ -29,27 +29,27 @@ static final class Node implements Located { private final Type type; private final List nodes; private final String token; - private final int startIndex; - private final int endIndex; + private final int start; + private final int end; - Node(Type type, int startIndex, int endIndex, String token) { - this(type, startIndex, endIndex, null, token); + Node(Type type, int start, int end, String token) { + this(type, start, end, null, token); } - Node(Type type, int startIndex, int endIndex, Node... nodes) { - this(type, startIndex, endIndex, asList(nodes)); + Node(Type type, int start, int end, Node... nodes) { + this(type, start, end, asList(nodes)); } - Node(Type type, int startIndex, int endIndex, List nodes) { - this(type, startIndex, endIndex, nodes, null); + Node(Type type, int start, int end, List nodes) { + this(type, start, end, nodes, null); } - private Node(Type type, int startIndex, int endIndex, List nodes, String token) { + private Node(Type type, int start, int end, List nodes, String token) { this.type = requireNonNull(type); this.nodes = nodes; this.token = token; - this.startIndex = startIndex; - this.endIndex = endIndex; + this.start = start; + this.end = end; } @@ -63,27 +63,23 @@ enum Type { } public int start() { - return startIndex; + return start; } public int end() { - return endIndex; + return end; } List nodes() { return nodes; } - boolean isLeaf() { - return nodes == null; - } - Type type() { return type; } String text() { - if (isLeaf()) + if (nodes == null) return token; return nodes().stream() @@ -101,7 +97,7 @@ private StringBuilder toString(int depth) { for (int i = 0; i < depth; i++) { sb.append("\t"); } - sb.append("AstNode{").append(startIndex).append(":").append(endIndex).append(", type=").append(type); + sb.append("AstNode{").append(start).append(":").append(end).append(", type=").append(type); if (token != null) { sb.append(", token=").append(token); @@ -129,8 +125,8 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; Node node = (Node) o; - return startIndex == node.startIndex && - endIndex == node.endIndex && + return start == node.start && + end == node.end && type == node.type && Objects.equals(nodes, node.nodes) && Objects.equals(token, node.token); @@ -138,23 +134,23 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(type, nodes, token, startIndex, endIndex); + return Objects.hash(type, nodes, token, start, end); } } static final class Token implements Located { - final int startIndex; - final int endIndex; final String text; final Token.Type type; + final int start; + final int end; - Token(String text, Token.Type type, int startIndex, int endIndex) { + Token(String text, Token.Type type, int start, int end) { this.text = requireNonNull(text); this.type = requireNonNull(type); - this.startIndex = startIndex; - this.endIndex = endIndex; + this.start = start; + this.end = end; } static boolean canEscape(Integer token) { @@ -162,12 +158,12 @@ static boolean canEscape(Integer token) { return true; } switch (token) { - case (int) ESCAPE_CHARACTER: - case (int) ALTERNATION_CHARACTER: - case (int) BEGIN_PARAMETER_CHARACTER: - case (int) END_PARAMETER_CHARACTER: - case (int) BEGIN_OPTIONAL_CHARACTER: - case (int) END_OPTIONAL_CHARACTER: + case (int) escapeCharacter: + case (int) alternationCharacter: + case (int) beginParameterCharacter: + case (int) endParameterCharacter: + case (int) beginOptionalCharacter: + case (int) endOptionalCharacter: return true; } return false; @@ -178,30 +174,30 @@ static Type typeOf(Integer token) { return Type.WHITE_SPACE; } switch (token) { - case (int) ALTERNATION_CHARACTER: + case (int) alternationCharacter: return Type.ALTERNATION; - case (int) BEGIN_PARAMETER_CHARACTER: + case (int) beginParameterCharacter: return Type.BEGIN_PARAMETER; - case (int) END_PARAMETER_CHARACTER: + case (int) endParameterCharacter: return Type.END_PARAMETER; - case (int) BEGIN_OPTIONAL_CHARACTER: + case (int) beginOptionalCharacter: return Type.BEGIN_OPTIONAL; - case (int) END_OPTIONAL_CHARACTER: + case (int) endOptionalCharacter: return Type.END_OPTIONAL; } return Type.TEXT; } static boolean isEscapeCharacter(int token) { - return token == ESCAPE_CHARACTER; + return token == escapeCharacter; } public int start() { - return startIndex; + return start; } public int end() { - return endIndex; + return end; } @Override @@ -211,22 +207,22 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; Token token = (Token) o; - return startIndex == token.startIndex && - endIndex == token.endIndex && + return start == token.start && + end == token.end && text.equals(token.text) && type == token.type; } @Override public int hashCode() { - return Objects.hash(startIndex, endIndex, text, type); + return Objects.hash(start, end, text, type); } @Override public String toString() { return new StringJoiner(", ", Token.class.getSimpleName() + "[", "]") - .add("startIndex=" + startIndex) - .add("endIndex=" + endIndex) + .add("startIndex=" + start) + .add("endIndex=" + end) .add("text='" + text + "'") .add("type=" + type) .toString(); @@ -236,11 +232,11 @@ enum Type { START_OF_LINE, END_OF_LINE, WHITE_SPACE, - BEGIN_OPTIONAL("" + BEGIN_OPTIONAL_CHARACTER, "optional text"), - END_OPTIONAL("" + END_OPTIONAL_CHARACTER, "optional text"), - BEGIN_PARAMETER("" + BEGIN_PARAMETER_CHARACTER, "a parameter"), - END_PARAMETER("" + END_PARAMETER_CHARACTER, "a parameter"), - ALTERNATION("" + ALTERNATION_CHARACTER, "alternation"), + BEGIN_OPTIONAL("" + beginOptionalCharacter, "optional text"), + END_OPTIONAL("" + endOptionalCharacter, "optional text"), + BEGIN_PARAMETER("" + beginParameterCharacter, "a parameter"), + END_PARAMETER("" + endParameterCharacter, "a parameter"), + ALTERNATION("" + alternationCharacter, "alternation"), TEXT; private final String symbol; diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 725f8c820d..eb132422b7 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -63,7 +63,7 @@ final class CucumberExpressionParser { return new Result(0); } Token token = tokens.get(current); - return new Result(1, new Node(ALTERNATIVE_NODE, token.start(), token.end(), "/")); + return new Result(1, new Node(ALTERNATIVE_NODE, token.start(), token.end(), token.text)); }; private static final List alternativeParsers = asList( @@ -80,7 +80,7 @@ final class CucumberExpressionParser { */ private static final Parser alternationParser = (expression, tokens, current) -> { int previous = current - 1; - if (!lookingAt(tokens, previous, START_OF_LINE, WHITE_SPACE)) { + if (!lookingAtAny(tokens, previous, START_OF_LINE, WHITE_SPACE)) { return new Result(0); } @@ -89,6 +89,7 @@ final class CucumberExpressionParser { if (result.ast.stream().noneMatch(astNode -> astNode.type() == ALTERNATIVE_NODE)) { return new Result(0); } + int start = tokens.get(current).start(); int end = tokens.get(subCurrent).start(); // Does not consume right hand boundary token @@ -172,7 +173,7 @@ private static Result parseTokensUntil( int size = tokens.size(); List ast = new ArrayList<>(); while (current < size) { - if (lookingAt(tokens, current, endTokens)) { + if (lookingAtAny(tokens, current, endTokens)) { break; } @@ -201,7 +202,7 @@ private static Result parseToken(String expression, List parsers, throw new IllegalStateException("No eligible parsers for " + tokens); } - private static boolean lookingAt(List tokens, int at, Type... tokenTypes) { + private static boolean lookingAtAny(List tokens, int at, Type... tokenTypes) { for (Type tokeType : tokenTypes) { if (lookingAt(tokens, at, tokeType)) { return true; @@ -224,13 +225,13 @@ private static List splitAlternatives(int start, int end, List alter List separators = new ArrayList<>(); List> alternatives = new ArrayList<>(); List alternative = new ArrayList<>(); - for (Node node : alternation) { - if (ALTERNATIVE_NODE.equals(node.type())) { - separators.add(node); + for (Node n : alternation) { + if (ALTERNATIVE_NODE.equals(n.type())) { + separators.add(n); alternatives.add(alternative); alternative = new ArrayList<>(); } else { - alternative.add(node); + alternative.add(n); } } alternatives.add(alternative); @@ -241,17 +242,17 @@ private static List splitAlternatives(int start, int end, List alter private static List createAlternativeNodes(int start, int end, List separators, List> alternatives) { List nodes = new ArrayList<>(); for (int i = 0; i < alternatives.size(); i++) { - List node = alternatives.get(i); + List n = alternatives.get(i); if (i == 0) { Node rightSeparator = separators.get(i); - nodes.add(new Node(ALTERNATIVE_NODE, start, rightSeparator.start(), node)); + nodes.add(new Node(ALTERNATIVE_NODE, start, rightSeparator.start(), n)); } else if (i == alternatives.size() - 1) { Node leftSeparator = separators.get(i - 1); - nodes.add(new Node(ALTERNATIVE_NODE, leftSeparator.end(), end, node)); + nodes.add(new Node(ALTERNATIVE_NODE, leftSeparator.end(), end, n)); } else { Node leftSeparator = separators.get(i - 1); Node rightSeparator = separators.get(i); - nodes.add(new Node(ALTERNATIVE_NODE, leftSeparator.end(), rightSeparator.start(), node)); + nodes.add(new Node(ALTERNATIVE_NODE, leftSeparator.end(), rightSeparator.start(), n)); } } return nodes; From 25dceb1a76e7cb42e04286a8c0c13e4305c0d62c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 24 Jul 2020 22:27:29 +0200 Subject: [PATCH 087/183] WIP --- .../go/cucumber_expression_parser_test.go | 322 +++++++++--------- .../go/cucumber_expression_tokenizer_test.go | 100 +++--- 2 files changed, 203 insertions(+), 219 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index 66ddf189d6..3b44b0f1f3 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -14,18 +14,14 @@ func TestCucumberExpressionParser(t *testing.T) { t.Run("empty string", func(t *testing.T) { assertAst(t, "", node{ - expressionNode, - -1, -1, - "", + expressionNode, -1, -1, "", []node{}, }) }) t.Run("phrase", func(t *testing.T) { assertAst(t, "three blind mice", node{ - expressionNode, - -1, -1, - "", + expressionNode, -1, -1, "", []node{ {textNode, -1, -1, "three", []node{}}, {textNode, -1, -1, " ", []node{}}, @@ -38,9 +34,7 @@ func TestCucumberExpressionParser(t *testing.T) { t.Run("optional", func(t *testing.T) { assertAst(t, "(blind)", node{ - expressionNode, - -1, -1, - "", + expressionNode, -1, -1, "", []node{ {optionalNode, -1, @@ -56,8 +50,7 @@ func TestCucumberExpressionParser(t *testing.T) { t.Run("parameter", func(t *testing.T) { assertAst(t, "{string}", node{ - expressionNode, -1, -1, - "", + expressionNode, -1, -1, "", []node{ {parameterNode, -1, -1, "", @@ -71,278 +64,269 @@ func TestCucumberExpressionParser(t *testing.T) { t.Run("anonymous parameter", func(t *testing.T) { assertAst(t, "{}", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", []node{ - {parameterNode, -1, -1, - []node{}, - "", - }, + {parameterNode, -1, -1, "", []node{}}, }, - "", }) }) t.Run("optional phrase", func(t *testing.T) { assertAst(t, "three (blind) mice", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", []node{ - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {optionalNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "blind"}, - }, ""}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "mice"}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {optionalNode, -1, -1, "", []node{ + {textNode, -1, -1, "blind", []node{}}, + }}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, }, - "", }) }) t.Run("slash", func(t *testing.T) { assertAst(t, "\\", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", []node{ - {textNode, -1, -1 []node{}, "\\"}, + {textNode, -1, -1, "\\", []node{}}, }, - "", }) }) t.Run("opening brace", func(t *testing.T) { assertAst(t, "{", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", []node{ - {textNode, -1, -1 []node{}, "{"}, + {textNode, -1, -1, "{", []node{}}, }, - "", }) }) t.Run("unfinished parameter", func(t *testing.T) { assertAst(t, "{string", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", []node{ - {textNode, -1, -1 []node{}, "{"}, - {textNode, -1, -1 []node{}, "string"}, + {textNode, -1, -1, "{", []node{}}, + {textNode, -1, -1, "string", []node{}}, }, - "", }) }) t.Run("opening parenthesis", func(t *testing.T) { assertAst(t, "(", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", []node{ - {textNode, -1, -1 []node{}, "("}, + {textNode, -1, -1, "(", []node{}}, }, - "", }) }) t.Run("escaped opening parenthesis", func(t *testing.T) { assertAst(t, "\\(", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", []node{ - {textNode, -1, -1 []node{}, "("}, + {textNode, -1, -1, "(", []node{}}, }, - "", }) }) t.Run("escaped optional phrase", func(t *testing.T) { assertAst(t, "three \\(blind) mice", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", []node{ - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "("}, - {textNode, -1, -1 []node{}, "blind"}, - {textNode, -1, -1 []node{}, ")"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "mice"}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "(", []node{}}, + {textNode, -1, -1, "blind", []node{}}, + {textNode, -1, -1, ")", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, }, - "", }) }) t.Run("escaped optional followed by optional", func(t *testing.T) { assertAst(t, "three \\((very) blind) mice", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "("}, - {optionalNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "very"}, - }, ""}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "blind"}, - {textNode, -1, -1 []node{}, ")"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "mice"}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "(", []node{}}, + {optionalNode, -1, -1, "", []node{ + {textNode, -1, -1, "very", []node{}}, + }}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "blind", []node{}}, + {textNode, -1, -1, ")", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, }, - "", }) }) t.Run("optional containing escaped optional", func(t *testing.T) { assertAst(t, "three ((very\\) blind) mice", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {optionalNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "("}, - {textNode, -1, -1 []node{}, "very"}, - {textNode, -1, -1 []node{}, ")"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "blind"}, - }, ""}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "mice"}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {optionalNode, -1, -1, "", []node{ + {textNode, -1, -1, "(", []node{}}, + {textNode, -1, -1, "very", []node{}}, + {textNode, -1, -1, ")", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "blind", []node{}}, + }}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, }, - "", }) }) t.Run("alternation", func(t *testing.T) { assertAst(t, "mice/rats", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {alternationNode, -1, -1 []node{ - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "mice"}, - }, ""}, - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "rats"}, - }, ""}, - }, ""}, + {alternationNode, -1, -1, "", + []node{ + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "mice", []node{}}, + }}, + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "rats", []node{}}, + }}, + }, + }, }, - "", }) }) t.Run("escaped alternation", func(t *testing.T) { assertAst(t, "mice\\/rats", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {textNode, -1, -1 []node{}, "mice"}, - {textNode, -1, -1 []node{}, "/"}, - {textNode, -1, -1 []node{}, "rats"}, + {textNode, -1, -1, "mice", []node{}}, + {textNode, -1, -1, "/", []node{}}, + {textNode, -1, -1, "rats", []node{}}, }, - "", }) }) t.Run("alternation phrase", func(t *testing.T) { assertAst(t, "three hungry/blind mice", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {alternationNode, -1, -1 []node{ - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "hungry"}, - }, ""}, - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "blind"}, - }, ""}, - }, ""}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "mice"}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {alternationNode, -1, -1, "", []node{ + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "hungry", []node{}}, + }}, + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "blind", []node{}}, + }}, + }}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, }, - "", }) }) t.Run("alternation with whitespace", func(t *testing.T) { assertAst(t, "\\ three\\ hungry/blind\\ mice\\ ", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {alternationNode, -1, -1 []node{ - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "hungry"}, - }, ""}, - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "blind"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "mice"}, - {textNode, -1, -1 []node{}, " "}, - }, ""}, - }, ""}, + {alternationNode, -1, -1, "", []node{ + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "hungry", []node{}}, + }}, + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "blind", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, + {textNode, -1, -1, " ", []node{}}, + }}, + }}, }, - "", }) }) t.Run("alternation with unused end optional", func(t *testing.T) { assertAst(t, "three )blind\\ mice/rats", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {alternationNode, -1, -1 []node{ - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, ")"}, - {textNode, -1, -1 []node{}, "blind"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "mice"}, - }, ""}, - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "rats"}, - }, ""}, - }, ""}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {alternationNode, -1, -1, "", []node{ + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, ")", []node{}}, + {textNode, -1, -1, "blind", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, + }}, + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "rats", []node{}}, + }}, + }}, }, - "", }) }) t.Run("alternation with unused start optional", func(t *testing.T) { assertAst(t, "three blind\\ mice/rats(", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {alternationNode, -1, -1 []node{ - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "blind"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "mice"}, - }, ""}, - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "rats"}, - {textNode, -1, -1 []node{}, "("}, - }, ""}, - }, ""}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {alternationNode, -1, -1, "", []node{ + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "blind", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "mice", []node{}}, + }}, + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "rats", []node{}}, + {textNode, -1, -1, "(", []node{}}, + }}, + }}, }, - "", }) }) t.Run("alternation with unused start optional", func(t *testing.T) { assertAst(t, "three blind\\ rat/cat(s)", node{ - expressionNode, -1, -1, + expressionNode, -1, -1, "", + []node{ - {textNode, -1, -1 []node{}, "three"}, - {textNode, -1, -1 []node{}, " "}, - {alternationNode, -1, -1 []node{ - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "blind"}, - {textNode, -1, -1 []node{}, " "}, - {textNode, -1, -1 []node{}, "rat"}, - }, ""}, - {alternativeNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "cat"}, - {optionalNode, -1, -1 []node{ - {textNode, -1, -1 []node{}, "s"}, - }, ""}, - }, ""}, - }, ""}, + {textNode, -1, -1, "three", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {alternationNode, -1, -1, "", []node{ + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "blind", []node{}}, + {textNode, -1, -1, " ", []node{}}, + {textNode, -1, -1, "rat", []node{}}, + }}, + {alternativeNode, -1, -1, "", []node{ + {textNode, -1, -1, "cat", []node{}}, + {optionalNode, -1, -1, "", []node{ + {textNode, -1, -1, "s", []node{}}, + }}, + }}, + }}, }, - "", }) }) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index ba1995d90a..add4860a11 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -17,100 +17,100 @@ func TestCucumberExpressionTokenizer(t *testing.T) { }) t.Run("phrase", func(t *testing.T) { assertContains(t, "three blind mice", []token{ - {"three", text}, - {" ", whiteSpace}, - {"blind", text}, - {" ", whiteSpace}, - {"mice", text}, + {"three", text, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"blind", text, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"mice", text, -1, -1}, }) }) t.Run("optional", func(t *testing.T) { assertContains(t, "(blind)", []token{ - {"(", beginOptional}, - {"blind", text}, - {")", endOptional}, + {"(", beginOptional, -1, -1}, + {"blind", text, -1, -1}, + {")", endOptional, -1, -1}, }) }) t.Run("escaped optional", func(t *testing.T) { assertContains(t, "\\(blind\\)", []token{ - {"\\(", beginOptionalEscaped}, - {"blind", text}, - {"\\)", endOptionalEscaped}, + {"\\(", beginOptionalEscaped, -1, -1}, + {"blind", text, -1, -1}, + {"\\)", endOptionalEscaped, -1, -1}, }) }) t.Run("optional phrase", func(t *testing.T) { assertContains(t, "three (blind) mice", []token{ - {"three", text}, - {" ", whiteSpace}, - {"(", beginOptional}, - {"blind", text}, - {")", endOptional}, - {" ", whiteSpace}, - {"mice", text}, + {"three", text, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"(", beginOptional, -1, -1}, + {"blind", text, -1, -1}, + {")", endOptional, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"mice", text, -1, -1}, }) }) t.Run("parameter", func(t *testing.T) { assertContains(t, "{string}", []token{ - {"{", beginParameter}, - {"string", text}, - {"}", endParameter}, + {"{", beginParameter, -1, -1}, + {"string", text, -1, -1}, + {"}", endParameter, -1, -1}, }) }) t.Run("escaped parameter", func(t *testing.T) { assertContains(t, "\\{string\\}", []token{ - {"\\{", beginParameterEscaped}, - {"string", text}, - {"\\}", endParameterEscaped}, + {"\\{", beginParameterEscaped, -1, -1}, + {"string", text, -1, -1}, + {"\\}", endParameterEscaped, -1, -1}, }) }) t.Run("parameter phrase", func(t *testing.T) { assertContains(t, "three {string} mice", []token{ - {"three", text}, - {" ", whiteSpace}, - {"{", beginParameter}, - {"string", text}, - {"}", endParameter}, - {" ", whiteSpace}, - {"mice", text}, + {"three", text, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"{", beginParameter, -1, -1}, + {"string", text, -1, -1}, + {"}", endParameter, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"mice", text, -1, -1}, }) }) t.Run("alternation", func(t *testing.T) { assertContains(t, "blind/cripple", []token{ - {"blind", text}, - {"/", alternation}, - {"cripple", text}, + {"blind", text, -1, -1}, + {"/", alternation, -1, -1}, + {"cripple", text, -1, -1}, }) }) t.Run("escaped alternation", func(t *testing.T) { assertContains(t, "blind\\ and\\ famished\\/cripple mice", []token{ - {"blind", text}, - {"\\ ", whiteSpaceEscaped}, - {"and", text}, - {"\\ ", whiteSpaceEscaped}, - {"famished", text}, - {"\\/", alternationEscaped}, - {"cripple", text}, - {" ", whiteSpace}, - {"mice", text}, + {"blind", text, -1, -1}, + {"\\ ", whiteSpaceEscaped, -1, -1}, + {"and", text, -1, -1}, + {"\\ ", whiteSpaceEscaped, -1, -1}, + {"famished", text, -1, -1}, + {"\\/", alternationEscaped, -1, -1}, + {"cripple", text, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"mice", text, -1, -1}, }) }) t.Run("alternation phrase", func(t *testing.T) { assertContains(t, "three blind/cripple mice", []token{ - {"three", text}, - {" ", whiteSpace}, - {"blind", text}, - {"/", alternation}, - {"cripple", text}, - {" ", whiteSpace}, - {"mice", text}, + {"three", text, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"blind", text, -1, -1}, + {"/", alternation, -1, -1}, + {"cripple", text, -1, -1}, + {" ", whiteSpace, -1, -1}, + {"mice", text, -1, -1}, }) }) From 239176cd41079a7ecea97d54a11e27310e51045c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 31 Jul 2020 15:48:42 +0200 Subject: [PATCH 088/183] Fix go compile --- .../go/cucumber_expression_tokenizer.go | 4 +- .../go/cucumber_expression_tokenizer_test.go | 41 ++++++++++++------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 6285edad7f..5c13789be6 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -1,8 +1,8 @@ package cucumberexpressions import ( - "regexp" - "strings" + //"regexp" + //"strings" ) type tokenizer func(expression string, current int) (int, token) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index add4860a11..06f826ac00 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -13,36 +13,44 @@ func TestCucumberExpressionTokenizer(t *testing.T) { } t.Run("empty string", func(t *testing.T) { - assertContains(t, "", []token{}) + assertContains(t, "", []token{ + {"", startOfLine, 0, 0}, + {"", endOfLine, 0, 0}, + }) }) t.Run("phrase", func(t *testing.T) { assertContains(t, "three blind mice", []token{ + {"", startOfLine, -1, -1}, {"three", text, -1, -1}, {" ", whiteSpace, -1, -1}, {"blind", text, -1, -1}, {" ", whiteSpace, -1, -1}, {"mice", text, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("optional", func(t *testing.T) { assertContains(t, "(blind)", []token{ + {"", startOfLine, -1, -1}, {"(", beginOptional, -1, -1}, {"blind", text, -1, -1}, {")", endOptional, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("escaped optional", func(t *testing.T) { assertContains(t, "\\(blind\\)", []token{ - {"\\(", beginOptionalEscaped, -1, -1}, - {"blind", text, -1, -1}, - {"\\)", endOptionalEscaped, -1, -1}, + {"", startOfLine, -1, -1}, + {"(blind)", text, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("optional phrase", func(t *testing.T) { assertContains(t, "three (blind) mice", []token{ + {"", startOfLine, -1, -1}, {"three", text, -1, -1}, {" ", whiteSpace, -1, -1}, {"(", beginOptional, -1, -1}, @@ -50,27 +58,31 @@ func TestCucumberExpressionTokenizer(t *testing.T) { {")", endOptional, -1, -1}, {" ", whiteSpace, -1, -1}, {"mice", text, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("parameter", func(t *testing.T) { assertContains(t, "{string}", []token{ + {"", startOfLine, -1, -1}, {"{", beginParameter, -1, -1}, {"string", text, -1, -1}, {"}", endParameter, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("escaped parameter", func(t *testing.T) { assertContains(t, "\\{string\\}", []token{ - {"\\{", beginParameterEscaped, -1, -1}, - {"string", text, -1, -1}, - {"\\}", endParameterEscaped, -1, -1}, + {"", startOfLine, -1, -1}, + {"{string}", text, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("parameter phrase", func(t *testing.T) { assertContains(t, "three {string} mice", []token{ + {"", startOfLine, -1, -1}, {"three", text, -1, -1}, {" ", whiteSpace, -1, -1}, {"{", beginParameter, -1, -1}, @@ -78,32 +90,32 @@ func TestCucumberExpressionTokenizer(t *testing.T) { {"}", endParameter, -1, -1}, {" ", whiteSpace, -1, -1}, {"mice", text, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("alternation", func(t *testing.T) { assertContains(t, "blind/cripple", []token{ + {"", startOfLine, -1, -1}, {"blind", text, -1, -1}, {"/", alternation, -1, -1}, {"cripple", text, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("escaped alternation", func(t *testing.T) { assertContains(t, "blind\\ and\\ famished\\/cripple mice", []token{ - {"blind", text, -1, -1}, - {"\\ ", whiteSpaceEscaped, -1, -1}, - {"and", text, -1, -1}, - {"\\ ", whiteSpaceEscaped, -1, -1}, - {"famished", text, -1, -1}, - {"\\/", alternationEscaped, -1, -1}, - {"cripple", text, -1, -1}, + {"", startOfLine, -1, -1}, + {"blind and famished/cripple", text, -1, -1}, {" ", whiteSpace, -1, -1}, {"mice", text, -1, -1}, + {"", endOfLine, -1, -1}, }) }) t.Run("alternation phrase", func(t *testing.T) { assertContains(t, "three blind/cripple mice", []token{ + {"", startOfLine, -1, -1}, {"three", text, -1, -1}, {" ", whiteSpace, -1, -1}, {"blind", text, -1, -1}, @@ -111,6 +123,7 @@ func TestCucumberExpressionTokenizer(t *testing.T) { {"cripple", text, -1, -1}, {" ", whiteSpace, -1, -1}, {"mice", text, -1, -1}, + {"", endOfLine, -1, -1}, }) }) From 712b7daf4b1cd8b2d1df645cac7f69d758a43051 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 8 Sep 2020 22:45:44 +0200 Subject: [PATCH 089/183] Use parameter type as boundary for alternation --- cucumber-expressions/README.md | 5 +- cucumber-expressions/examples.txt | 4 + .../CucumberExpression.java | 2 - .../CucumberExpressionException.java | 9 --- .../CucumberExpressionParser.java | 11 ++- .../CucumberExpressionTest.java | 76 ++++++++++++------- 6 files changed, 62 insertions(+), 45 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index c85649ead1..1bea12ffb9 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -7,8 +7,9 @@ A Cucumber Expression has the following AST: ``` cucumber-expression := ( alternation | optional | parameter | text )* -alternation := (?<=boundary) + alternative* + ( '/' + alternative* )+ + (?=boundary) -boundary := whitespace | ^ | $ +alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary) +left-boundary := whitespace | } | ^ +right-boundary := whitespace | { | $ alternative: = optional | parameter | text optional := '(' + option* + ')' option := parameter | text diff --git a/cucumber-expressions/examples.txt b/cucumber-expressions/examples.txt index c6acb6958e..367e84c2fa 100644 --- a/cucumber-expressions/examples.txt +++ b/cucumber-expressions/examples.txt @@ -37,3 +37,7 @@ a purchase for $33 Some ${float} of cukes at {int}° Celsius Some $3.50 of cukes at 42° Celsius [3.5,42] +---- +I select the {int}st/nd/rd/th +I select the 3rd Cucumber +[3] diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 3a586d5eaa..879322ada3 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -14,7 +14,6 @@ import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeIsEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayExclusivelyContainOptionals; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty; -import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInAlternative; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInOptional; import static java.util.stream.Collectors.joining; @@ -74,7 +73,6 @@ private String rewriteAlternation(Node node) { if (alternative.nodes().isEmpty()) { throw createAlternativeIsEmpty(alternative, source); } - assertNoParameters(alternative, astNode -> createParameterIsNotAllowedInAlternative(astNode, source)); assertNotEmpty(alternative, astNode -> createAlternativeMayExclusivelyContainOptionals(astNode, source)); } return node.nodes() diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index e766093d68..88766d5ecb 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -47,15 +47,6 @@ static CucumberExpressionException createAlternativeIsEmpty(Node node, String ex "If you did not mean to use an alternative you can use '\\/' to escape the the '/'")); } - static CucumberExpressionException createParameterIsNotAllowedInAlternative(Node node, String expression) { - return new CucumberExpressionException(message( - node.start(), - expression, - pointAt(node), - "An alternative may not contain a parameter type", - "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'")); - } - static CucumberExpressionException createParameterIsNotAllowedInOptional(Node node, String expression) { return new CucumberExpressionException(message( node.start(), diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index eb132422b7..d6825e6ea7 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -74,17 +74,18 @@ final class CucumberExpressionParser { ); /* - * alternation := (?<=boundary) + alternative* + ( '/' + alternative* )+ + (?=boundary) - * boundary := whitespace | ^ | $ + * alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary) + * left-boundary := whitespace | } | ^ + * right-boundary := whitespace | { | $ * alternative: = optional | parameter | text */ private static final Parser alternationParser = (expression, tokens, current) -> { int previous = current - 1; - if (!lookingAtAny(tokens, previous, START_OF_LINE, WHITE_SPACE)) { + if (!lookingAtAny(tokens, previous, START_OF_LINE, WHITE_SPACE, END_PARAMETER)) { return new Result(0); } - Result result = parseTokensUntil(expression, alternativeParsers, tokens, current, WHITE_SPACE, END_OF_LINE); + Result result = parseTokensUntil(expression, alternativeParsers, tokens, current, WHITE_SPACE, END_OF_LINE, BEGIN_PARAMETER); int subCurrent = current + result.consumed; if (result.ast.stream().noneMatch(astNode -> astNode.type() == ALTERNATIVE_NODE)) { return new Result(0); @@ -213,6 +214,8 @@ private static boolean lookingAtAny(List tokens, int at, Type... tokenTyp private static boolean lookingAt(List tokens, int at, Type token) { if (at < 0) { + // If configured correctly this will never happen + // Keep for completeness return token == START_OF_LINE; } if (at >= tokens.size()) { diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 518044ced7..4d54af1d3b 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -91,7 +91,7 @@ public void does_not_allow_alternation_with_empty_alternative() { } @Test - public void allows_optional_adjacent_to_alternation() { + public void does_not_allow_alternation_with_empty_alternative_by_adjacent_optional() { Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); assertThat(thrownException.getMessage(), @@ -104,6 +104,41 @@ public void allows_optional_adjacent_to_alternation() { "If you did not mean to use an optional you can use '\\(' to escape the the '('"))); } + @Test + public void does_not_allow_alternation_with_empty_alternative_by_adjacent_right_parameter() { + + final Executable testMethod = () -> match("x/{int}", "3"); + + final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); + assertThat(thrownException.getMessage(), is(equalTo("" + + "This Cucumber Expression has a problem at column 3:\n" + + "\n" + + "x/{int}\n" + + " ^\n" + + "Alternative may not be empty.\n" + + "If you did not mean to use an alternative you can use '\\/' to escape the the '/'"))); + } + + @Test + public void does_allow_parameter_adjacent_to_alternation() { + assertEquals(singletonList(3), match("{int}st/nd/rd/th", "3rd")); + } + + @Test + public void does_not_allow_alternation_with_empty_alternative_by_adjacent_left_parameter() { + + final Executable testMethod = () -> match("{int}/x", "3"); + + final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); + assertThat(thrownException.getMessage(), is(equalTo("" + + "This Cucumber Expression has a problem at column 6:\n" + + "\n" + + "{int}/x\n" + + " ^\n" + + "Alternative may not be empty.\n" + + "If you did not mean to use an alternative you can use '\\/' to escape the the '/'"))); + } + @Test public void matches_double_quoted_string() { assertEquals(singletonList("blind"), match("three {string} mice", "three \"blind\" mice")); @@ -172,6 +207,15 @@ public void matches_double_quoted_empty_string_as_empty_string_along_with_other_ match("three {string} and {string} mice", "three \"\" and \"handsome\" mice")); } + + @Test + public void alternation_seperator_can_be_used_in_parameter() { + parameterTypeRegistry + .defineParameterType(new ParameterType<>("a/b", "(.*)", String.class, (String arg) -> arg)); + assertEquals(singletonList("three mice"), + match("{a/b}", "three mice")); + } + @Test public void matches_escaped_parenthesis() { assertEquals(emptyList(), @@ -271,33 +315,9 @@ public void allows_escaped_optional_parameter_types() { } @Test - public void does_not_allow_text_parameter_type_alternation() { - - final Executable testMethod = () -> match("x/{int}", "3"); - - final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has a problem at column 3:\n" + - "\n" + - "x/{int}\n" + - " ^---^\n" + - "An alternative may not contain a parameter type.\n" + - "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'"))); - } - - @Test - public void does_not_allow_parameter_type_text_alternation() { - - final Executable testMethod = () -> match("{int}/x", "3"); - - final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has a problem at column 1:\n" + - "\n" + - "{int}/x\n" + - "^---^\n" + - "An alternative may not contain a parameter type.\n" + - "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'"))); + public void allows_parameter_type_in_alternation() { + assertEquals(singletonList(18), match("a/i{int}n/y", "i18n")); + assertEquals(singletonList(11), match("a/i{int}n/y", "a11y")); } @Test From c210fd644c33bd59050a54e74cc31131f85f8a2b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 10:53:16 +0200 Subject: [PATCH 090/183] Move unit tests to the right unit --- .../CucumberExpressionParserTest.java | 22 ----------------- .../CucumberExpressionTokenizerTest.java | 24 +++++++++++++++++++ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index dc113d7735..eadb7a2e28 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -86,28 +86,6 @@ void optionalPhrase() { )); } - @Test - void escapedEndOfLine() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + - "\n" + - "\\\n" + - " ^\n" + - "The end of line can not be escaped.\n" + - "You can use '\\\\' to escape the the '\\'")); - } - - @Test - void escapeNonReservedCharacter() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("\\[")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + - "\n" + - "\\[\n" + - " ^\n" + - "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped.\n" + - "If you did mean to use an '\\' you can use '\\\\' to escape it")); - } - @Test void escapedBackSlash() { assertThat(astOf("\\\\"), equalTo( diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index f894256821..1f31fe7c8b 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -14,6 +14,8 @@ import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; class CucumberExpressionTokenizerTest { @@ -167,4 +169,26 @@ void escapedSpace() { )); } + @Test + void escapedEndOfLine() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> tokenizer.tokenize("\\")); + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + + "\n" + + "\\\n" + + " ^\n" + + "The end of line can not be escaped.\n" + + "You can use '\\\\' to escape the the '\\'")); + } + + @Test + void escapeNonReservedCharacter() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> tokenizer.tokenize("\\[")); + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + + "\n" + + "\\[\n" + + " ^\n" + + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped.\n" + + "If you did mean to use an '\\' you can use '\\\\' to escape it")); + } + } From 54beaf4ede6095dfc77407bf1edbcdce2a07736b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 11:49:54 +0200 Subject: [PATCH 091/183] Self generate test data --- cucumber-expressions/java/pom.xml | 6 + .../io/cucumber/cucumberexpressions/Ast.java | 2 +- .../CucumberExpressionTokenizerTest.java | 159 ++++++++++++++++-- .../java/testdata/alternation.yaml | 9 + .../java/testdata/alternationPhrase.yaml | 13 ++ .../java/testdata/emptyString.yaml | 6 + .../escapeCharIsStartIndexOfTextToken.yaml | 9 + .../testdata/escapeNonReservedCharacter.yaml | 10 ++ .../java/testdata/escapedAlternation.yaml | 9 + .../java/testdata/escapedEndOfLine.yaml | 10 ++ .../java/testdata/escapedOptional.yaml | 7 + .../java/testdata/escapedParameter.yaml | 7 + .../java/testdata/escapedSpace.yaml | 7 + .../java/testdata/optional.yaml | 9 + .../java/testdata/optionalPhrase.yaml | 13 ++ .../java/testdata/parameter.yaml | 9 + .../java/testdata/parameterPhrase.yaml | 13 ++ .../java/testdata/phrase.yaml | 11 ++ 18 files changed, 293 insertions(+), 16 deletions(-) create mode 100644 cucumber-expressions/java/testdata/alternation.yaml create mode 100644 cucumber-expressions/java/testdata/alternationPhrase.yaml create mode 100644 cucumber-expressions/java/testdata/emptyString.yaml create mode 100644 cucumber-expressions/java/testdata/escapeCharIsStartIndexOfTextToken.yaml create mode 100644 cucumber-expressions/java/testdata/escapeNonReservedCharacter.yaml create mode 100644 cucumber-expressions/java/testdata/escapedAlternation.yaml create mode 100644 cucumber-expressions/java/testdata/escapedEndOfLine.yaml create mode 100644 cucumber-expressions/java/testdata/escapedOptional.yaml create mode 100644 cucumber-expressions/java/testdata/escapedParameter.yaml create mode 100644 cucumber-expressions/java/testdata/escapedSpace.yaml create mode 100644 cucumber-expressions/java/testdata/optional.yaml create mode 100644 cucumber-expressions/java/testdata/optionalPhrase.yaml create mode 100644 cucumber-expressions/java/testdata/parameter.yaml create mode 100644 cucumber-expressions/java/testdata/parameterPhrase.yaml create mode 100644 cucumber-expressions/java/testdata/phrase.yaml diff --git a/cucumber-expressions/java/pom.xml b/cucumber-expressions/java/pom.xml index 3523f762af..200561b4be 100644 --- a/cucumber-expressions/java/pom.xml +++ b/cucumber-expressions/java/pom.xml @@ -57,6 +57,12 @@ 2.11.2 test + + org.yaml + snakeyaml + 1.21 + test + org.junit.jupiter junit-jupiter-engine diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 749736871f..ec8db1c09f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -221,10 +221,10 @@ public int hashCode() { @Override public String toString() { return new StringJoiner(", ", Token.class.getSimpleName() + "[", "]") + .add("type=" + type) .add("startIndex=" + start) .add("endIndex=" + end) .add("text='" + text + "'") - .add("type=" + type) .toString(); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 1f31fe7c8b..f0147c0d01 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -1,7 +1,25 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.Ast.Token; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; @@ -12,6 +30,8 @@ import static io.cucumber.cucumberexpressions.Ast.Token.Type.START_OF_LINE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; @@ -20,10 +40,119 @@ class CucumberExpressionTokenizerTest { private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); + private String displayName; + + private static Stream test() throws IOException { + List expectations = new ArrayList<>(); + Path testdata = Paths.get("testdata"); + Yaml yaml = new Yaml(); + try (DirectoryStream stream = Files.newDirectoryStream(testdata)) { + for (Path path : stream) { + InputStream inputStream = Files.newInputStream(path); + Map map = yaml.loadAs(inputStream, Map.class); + Expectation expectation = new Expectation( + (String) map.get("expression"), + (List) map.get("tokens"), + (String) map.get("exception")); + expectations.add(expectation); + } + } + return expectations.stream(); + } + + @ParameterizedTest + @MethodSource + void test(Expectation expectation) { + if (expectation.getException() == null) { + List tokens = tokenizer + .tokenize(expectation.getExpression()) + .stream() + .map(Token::toString) + .collect(Collectors.toList()); + assertThat(tokens, is(expectation.getTokens())); + } else { + CucumberExpressionException exception = assertThrows( + CucumberExpressionException.class, + () -> tokenizer.tokenize(expectation.getExpression())); + assertThat(exception.getMessage(), is(exception.getMessage())); + } + } + + @BeforeEach + void setup(TestInfo testInfo) { + String displayName = testInfo.getDisplayName(); + this.displayName = displayName.substring(0, displayName.length() - 2); + + } + + public static class Expectation { + String expression; + List tokens; + String exception; + + Expectation(String expression, List tokens, String exception) { + this.expression = expression; + this.tokens = tokens; + this.exception = exception; + } + + public String getExpression() { + return expression; + } + + public List getTokens() { + return tokens; + } + + public String getException() { + return exception; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + public void setTokens(List tokens) { + this.tokens = tokens; + } + + public void setException(String exception) { + this.exception = exception; + } + + } + + private List tokenize(String s) { + List tokensString = null; + List tokens = null; + String t = null; + RuntimeException orig = null; + try { + tokens = tokenizer.tokenize(s); + tokensString = tokens.stream().map(Token::toString).collect(Collectors.toList()); + } catch (RuntimeException e) { + orig = e; + t = e.getMessage(); + } + + DumperOptions dumperOptions = new DumperOptions(); + String yaml = new Yaml(dumperOptions).dumpAsMap(new Expectation(s, tokensString, t)); + try { + Files.write(Paths.get("testdata", displayName + ".yaml"), singletonList(yaml), UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + + if (orig != null) { + throw orig; + } + + return tokens; + } @Test void emptyString() { - assertThat(tokenizer.tokenize(""), contains( + assertThat(tokenize(""), contains( new Token("", START_OF_LINE, 0, 0), new Token("", END_OF_LINE, 0, 0) )); @@ -31,7 +160,7 @@ void emptyString() { @Test void phrase() { - assertThat(tokenizer.tokenize("three blind mice"), contains( + assertThat(tokenize("three blind mice"), contains( new Token("", START_OF_LINE, 0, 0), new Token("three", TEXT, 0, 5), new Token(" ", WHITE_SPACE, 5, 6), @@ -44,7 +173,7 @@ void phrase() { @Test void optional() { - assertThat(tokenizer.tokenize("(blind)"), contains( + assertThat(tokenize("(blind)"), contains( new Token("", START_OF_LINE, 0, 0), new Token("(", BEGIN_OPTIONAL, 0, 1), new Token("blind", TEXT, 1, 6), @@ -55,7 +184,7 @@ void optional() { @Test void escapedOptional() { - assertThat(tokenizer.tokenize("\\(blind\\)"), contains( + assertThat(tokenize("\\(blind\\)"), contains( new Token("", START_OF_LINE, 0, 0), new Token("(blind)", TEXT, 0, 9), new Token("", END_OF_LINE, 9, 9) @@ -64,7 +193,7 @@ void escapedOptional() { @Test void optionalPhrase() { - assertThat(tokenizer.tokenize("three (blind) mice"), contains( + assertThat(tokenize("three (blind) mice"), contains( new Token("", START_OF_LINE, 0, 0), new Token("three", TEXT, 0, 5), new Token(" ", WHITE_SPACE, 5, 6), @@ -79,7 +208,7 @@ void optionalPhrase() { @Test void parameter() { - assertThat(tokenizer.tokenize("{string}"), contains( + assertThat(tokenize("{string}"), contains( new Token("", START_OF_LINE, 0, 0), new Token("{", BEGIN_PARAMETER, 0, 1), new Token("string", TEXT, 1, 7), @@ -90,7 +219,7 @@ void parameter() { @Test void escapedParameter() { - assertThat(tokenizer.tokenize("\\{string\\}"), contains( + assertThat(tokenize("\\{string\\}"), contains( new Token("", START_OF_LINE, 0, 0), new Token("{string}", TEXT, 0, 10), new Token("", END_OF_LINE, 10, 10) @@ -99,7 +228,7 @@ void escapedParameter() { @Test void parameterPhrase() { - assertThat(tokenizer.tokenize("three {string} mice"), contains( + assertThat(tokenize("three {string} mice"), contains( new Token("", START_OF_LINE, 0, 0), new Token("three", TEXT, 0, 5), new Token(" ", WHITE_SPACE, 5, 6), @@ -114,7 +243,7 @@ void parameterPhrase() { @Test void alternation() { - assertThat(tokenizer.tokenize("blind/cripple"), contains( + assertThat(tokenize("blind/cripple"), contains( new Token("", START_OF_LINE, 0, 0), new Token("blind", TEXT, 0, 5), new Token("/", ALTERNATION, 5, 6), @@ -125,7 +254,7 @@ void alternation() { @Test void escapedAlternation() { - assertThat(tokenizer.tokenize("blind\\ and\\ famished\\/cripple mice"), contains( + assertThat(tokenize("blind\\ and\\ famished\\/cripple mice"), contains( new Token("", START_OF_LINE, 0, 0), new Token("blind and famished/cripple", TEXT, 0, 29), new Token(" ", WHITE_SPACE, 29, 30), @@ -136,7 +265,7 @@ void escapedAlternation() { @Test void escapeCharIsStartIndexOfTextToken() { - assertThat(tokenizer.tokenize(" \\/ "), contains( + assertThat(tokenize(" \\/ "), contains( new Token("", START_OF_LINE, 0, 0), new Token(" ", WHITE_SPACE, 0, 1), new Token("/", TEXT, 1, 3), @@ -147,7 +276,7 @@ void escapeCharIsStartIndexOfTextToken() { @Test void alternationPhrase() { - assertThat(tokenizer.tokenize("three blind/cripple mice"), contains( + assertThat(tokenize("three blind/cripple mice"), contains( new Token("", START_OF_LINE, 0, 0), new Token("three", TEXT, 0, 5), new Token(" ", WHITE_SPACE, 5, 6), @@ -162,7 +291,7 @@ void alternationPhrase() { @Test void escapedSpace() { - assertThat(tokenizer.tokenize("\\ "), contains( + assertThat(tokenize("\\ "), contains( new Token("", START_OF_LINE, 0, 0), new Token(" ", TEXT, 0, 2), new Token("", END_OF_LINE, 2, 2) @@ -171,7 +300,7 @@ void escapedSpace() { @Test void escapedEndOfLine() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> tokenizer.tokenize("\\")); + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> tokenize("\\")); assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + "\n" + "\\\n" + @@ -182,7 +311,7 @@ void escapedEndOfLine() { @Test void escapeNonReservedCharacter() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> tokenizer.tokenize("\\[")); + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> tokenize("\\[")); assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + "\n" + "\\[\n" + diff --git a/cucumber-expressions/java/testdata/alternation.yaml b/cucumber-expressions/java/testdata/alternation.yaml new file mode 100644 index 0000000000..e2a0ba1a0a --- /dev/null +++ b/cucumber-expressions/java/testdata/alternation.yaml @@ -0,0 +1,9 @@ +exception: null +expression: blind/cripple +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=5, text='blind'] +- Token[type=ALTERNATION, startIndex=5, endIndex=6, text='/'] +- Token[type=TEXT, startIndex=6, endIndex=13, text='cripple'] +- Token[type=END_OF_LINE, startIndex=13, endIndex=13, text=''] + diff --git a/cucumber-expressions/java/testdata/alternationPhrase.yaml b/cucumber-expressions/java/testdata/alternationPhrase.yaml new file mode 100644 index 0000000000..3bc7ce0c43 --- /dev/null +++ b/cucumber-expressions/java/testdata/alternationPhrase.yaml @@ -0,0 +1,13 @@ +exception: null +expression: three blind/cripple mice +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=5, text='three'] +- Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] +- Token[type=TEXT, startIndex=6, endIndex=11, text='blind'] +- Token[type=ALTERNATION, startIndex=11, endIndex=12, text='/'] +- Token[type=TEXT, startIndex=12, endIndex=19, text='cripple'] +- Token[type=WHITE_SPACE, startIndex=19, endIndex=20, text=' '] +- Token[type=TEXT, startIndex=20, endIndex=24, text='mice'] +- Token[type=END_OF_LINE, startIndex=24, endIndex=24, text=''] + diff --git a/cucumber-expressions/java/testdata/emptyString.yaml b/cucumber-expressions/java/testdata/emptyString.yaml new file mode 100644 index 0000000000..e35f03d290 --- /dev/null +++ b/cucumber-expressions/java/testdata/emptyString.yaml @@ -0,0 +1,6 @@ +exception: null +expression: '' +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=END_OF_LINE, startIndex=0, endIndex=0, text=''] + diff --git a/cucumber-expressions/java/testdata/escapeCharIsStartIndexOfTextToken.yaml b/cucumber-expressions/java/testdata/escapeCharIsStartIndexOfTextToken.yaml new file mode 100644 index 0000000000..0f735266e9 --- /dev/null +++ b/cucumber-expressions/java/testdata/escapeCharIsStartIndexOfTextToken.yaml @@ -0,0 +1,9 @@ +exception: null +expression: ' \/ ' +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=WHITE_SPACE, startIndex=0, endIndex=1, text=' '] +- Token[type=TEXT, startIndex=1, endIndex=3, text='/'] +- Token[type=WHITE_SPACE, startIndex=3, endIndex=4, text=' '] +- Token[type=END_OF_LINE, startIndex=4, endIndex=4, text=''] + diff --git a/cucumber-expressions/java/testdata/escapeNonReservedCharacter.yaml b/cucumber-expressions/java/testdata/escapeNonReservedCharacter.yaml new file mode 100644 index 0000000000..a1f3a9456d --- /dev/null +++ b/cucumber-expressions/java/testdata/escapeNonReservedCharacter.yaml @@ -0,0 +1,10 @@ +exception: |- + This Cucumber Expression has a problem at column 2: + + \[ + ^ + Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. + If you did mean to use an '\' you can use '\\' to escape it +expression: \[ +tokens: null + diff --git a/cucumber-expressions/java/testdata/escapedAlternation.yaml b/cucumber-expressions/java/testdata/escapedAlternation.yaml new file mode 100644 index 0000000000..dd2d61e0d7 --- /dev/null +++ b/cucumber-expressions/java/testdata/escapedAlternation.yaml @@ -0,0 +1,9 @@ +exception: null +expression: blind\ and\ famished\/cripple mice +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=29, text='blind and famished/cripple'] +- Token[type=WHITE_SPACE, startIndex=29, endIndex=30, text=' '] +- Token[type=TEXT, startIndex=30, endIndex=34, text='mice'] +- Token[type=END_OF_LINE, startIndex=34, endIndex=34, text=''] + diff --git a/cucumber-expressions/java/testdata/escapedEndOfLine.yaml b/cucumber-expressions/java/testdata/escapedEndOfLine.yaml new file mode 100644 index 0000000000..ef6039ef02 --- /dev/null +++ b/cucumber-expressions/java/testdata/escapedEndOfLine.yaml @@ -0,0 +1,10 @@ +exception: |- + This Cucumber Expression has a problem at column 2: + + \ + ^ + The end of line can not be escaped. + You can use '\\' to escape the the '\' +expression: \ +tokens: null + diff --git a/cucumber-expressions/java/testdata/escapedOptional.yaml b/cucumber-expressions/java/testdata/escapedOptional.yaml new file mode 100644 index 0000000000..6c044516e9 --- /dev/null +++ b/cucumber-expressions/java/testdata/escapedOptional.yaml @@ -0,0 +1,7 @@ +exception: null +expression: \(blind\) +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=9, text='(blind)'] +- Token[type=END_OF_LINE, startIndex=9, endIndex=9, text=''] + diff --git a/cucumber-expressions/java/testdata/escapedParameter.yaml b/cucumber-expressions/java/testdata/escapedParameter.yaml new file mode 100644 index 0000000000..4ca42d0f4a --- /dev/null +++ b/cucumber-expressions/java/testdata/escapedParameter.yaml @@ -0,0 +1,7 @@ +exception: null +expression: \{string\} +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=10, text='{string}'] +- Token[type=END_OF_LINE, startIndex=10, endIndex=10, text=''] + diff --git a/cucumber-expressions/java/testdata/escapedSpace.yaml b/cucumber-expressions/java/testdata/escapedSpace.yaml new file mode 100644 index 0000000000..fe74dfa361 --- /dev/null +++ b/cucumber-expressions/java/testdata/escapedSpace.yaml @@ -0,0 +1,7 @@ +exception: null +expression: '\ ' +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=2, text=' '] +- Token[type=END_OF_LINE, startIndex=2, endIndex=2, text=''] + diff --git a/cucumber-expressions/java/testdata/optional.yaml b/cucumber-expressions/java/testdata/optional.yaml new file mode 100644 index 0000000000..1be2c01591 --- /dev/null +++ b/cucumber-expressions/java/testdata/optional.yaml @@ -0,0 +1,9 @@ +exception: null +expression: (blind) +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=BEGIN_OPTIONAL, startIndex=0, endIndex=1, text='('] +- Token[type=TEXT, startIndex=1, endIndex=6, text='blind'] +- Token[type=END_OPTIONAL, startIndex=6, endIndex=7, text=')'] +- Token[type=END_OF_LINE, startIndex=7, endIndex=7, text=''] + diff --git a/cucumber-expressions/java/testdata/optionalPhrase.yaml b/cucumber-expressions/java/testdata/optionalPhrase.yaml new file mode 100644 index 0000000000..226a133e03 --- /dev/null +++ b/cucumber-expressions/java/testdata/optionalPhrase.yaml @@ -0,0 +1,13 @@ +exception: null +expression: three (blind) mice +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=5, text='three'] +- Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] +- Token[type=BEGIN_OPTIONAL, startIndex=6, endIndex=7, text='('] +- Token[type=TEXT, startIndex=7, endIndex=12, text='blind'] +- Token[type=END_OPTIONAL, startIndex=12, endIndex=13, text=')'] +- Token[type=WHITE_SPACE, startIndex=13, endIndex=14, text=' '] +- Token[type=TEXT, startIndex=14, endIndex=18, text='mice'] +- Token[type=END_OF_LINE, startIndex=18, endIndex=18, text=''] + diff --git a/cucumber-expressions/java/testdata/parameter.yaml b/cucumber-expressions/java/testdata/parameter.yaml new file mode 100644 index 0000000000..f9d7a83ee0 --- /dev/null +++ b/cucumber-expressions/java/testdata/parameter.yaml @@ -0,0 +1,9 @@ +exception: null +expression: '{string}' +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=BEGIN_PARAMETER, startIndex=0, endIndex=1, text='{'] +- Token[type=TEXT, startIndex=1, endIndex=7, text='string'] +- Token[type=END_PARAMETER, startIndex=7, endIndex=8, text='}'] +- Token[type=END_OF_LINE, startIndex=8, endIndex=8, text=''] + diff --git a/cucumber-expressions/java/testdata/parameterPhrase.yaml b/cucumber-expressions/java/testdata/parameterPhrase.yaml new file mode 100644 index 0000000000..1fc21800f0 --- /dev/null +++ b/cucumber-expressions/java/testdata/parameterPhrase.yaml @@ -0,0 +1,13 @@ +exception: null +expression: three {string} mice +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=5, text='three'] +- Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] +- Token[type=BEGIN_PARAMETER, startIndex=6, endIndex=7, text='{'] +- Token[type=TEXT, startIndex=7, endIndex=13, text='string'] +- Token[type=END_PARAMETER, startIndex=13, endIndex=14, text='}'] +- Token[type=WHITE_SPACE, startIndex=14, endIndex=15, text=' '] +- Token[type=TEXT, startIndex=15, endIndex=19, text='mice'] +- Token[type=END_OF_LINE, startIndex=19, endIndex=19, text=''] + diff --git a/cucumber-expressions/java/testdata/phrase.yaml b/cucumber-expressions/java/testdata/phrase.yaml new file mode 100644 index 0000000000..44359d7d03 --- /dev/null +++ b/cucumber-expressions/java/testdata/phrase.yaml @@ -0,0 +1,11 @@ +exception: null +expression: three blind mice +tokens: +- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=TEXT, startIndex=0, endIndex=5, text='three'] +- Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] +- Token[type=TEXT, startIndex=6, endIndex=11, text='blind'] +- Token[type=WHITE_SPACE, startIndex=11, endIndex=12, text=' '] +- Token[type=TEXT, startIndex=12, endIndex=16, text='mice'] +- Token[type=END_OF_LINE, startIndex=16, endIndex=16, text=''] + From ae15b9ce6596d6a275021516d14fea4d7fc3ffc2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 12:01:17 +0200 Subject: [PATCH 092/183] Self generate test data --- .../cucumberexpressions/CucumberExpressionTokenizerTest.java | 4 ++-- .../java/testdata/{ => tokens}/alternation.yaml | 0 .../java/testdata/{ => tokens}/alternationPhrase.yaml | 0 .../java/testdata/{ => tokens}/emptyString.yaml | 0 .../{ => tokens}/escapeCharIsStartIndexOfTextToken.yaml | 0 .../testdata/{ => tokens}/escapeNonReservedCharacter.yaml | 0 .../java/testdata/{ => tokens}/escapedAlternation.yaml | 0 .../java/testdata/{ => tokens}/escapedEndOfLine.yaml | 0 .../java/testdata/{ => tokens}/escapedOptional.yaml | 0 .../java/testdata/{ => tokens}/escapedParameter.yaml | 0 .../java/testdata/{ => tokens}/escapedSpace.yaml | 0 cucumber-expressions/java/testdata/{ => tokens}/optional.yaml | 0 .../java/testdata/{ => tokens}/optionalPhrase.yaml | 0 .../java/testdata/{ => tokens}/parameter.yaml | 0 .../java/testdata/{ => tokens}/parameterPhrase.yaml | 0 cucumber-expressions/java/testdata/{ => tokens}/phrase.yaml | 0 16 files changed, 2 insertions(+), 2 deletions(-) rename cucumber-expressions/java/testdata/{ => tokens}/alternation.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/alternationPhrase.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/emptyString.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/escapeCharIsStartIndexOfTextToken.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/escapeNonReservedCharacter.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/escapedAlternation.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/escapedEndOfLine.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/escapedOptional.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/escapedParameter.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/escapedSpace.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/optional.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/optionalPhrase.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/parameter.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/parameterPhrase.yaml (100%) rename cucumber-expressions/java/testdata/{ => tokens}/phrase.yaml (100%) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index f0147c0d01..0b2ec8436d 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -44,7 +44,7 @@ class CucumberExpressionTokenizerTest { private static Stream test() throws IOException { List expectations = new ArrayList<>(); - Path testdata = Paths.get("testdata"); + Path testdata = Paths.get("testdata/tokens"); Yaml yaml = new Yaml(); try (DirectoryStream stream = Files.newDirectoryStream(testdata)) { for (Path path : stream) { @@ -138,7 +138,7 @@ private List tokenize(String s) { DumperOptions dumperOptions = new DumperOptions(); String yaml = new Yaml(dumperOptions).dumpAsMap(new Expectation(s, tokensString, t)); try { - Files.write(Paths.get("testdata", displayName + ".yaml"), singletonList(yaml), UTF_8); + Files.write(Paths.get("testdata", "tokens", displayName + ".yaml"), singletonList(yaml), UTF_8); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/cucumber-expressions/java/testdata/alternation.yaml b/cucumber-expressions/java/testdata/tokens/alternation.yaml similarity index 100% rename from cucumber-expressions/java/testdata/alternation.yaml rename to cucumber-expressions/java/testdata/tokens/alternation.yaml diff --git a/cucumber-expressions/java/testdata/alternationPhrase.yaml b/cucumber-expressions/java/testdata/tokens/alternationPhrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/alternationPhrase.yaml rename to cucumber-expressions/java/testdata/tokens/alternationPhrase.yaml diff --git a/cucumber-expressions/java/testdata/emptyString.yaml b/cucumber-expressions/java/testdata/tokens/emptyString.yaml similarity index 100% rename from cucumber-expressions/java/testdata/emptyString.yaml rename to cucumber-expressions/java/testdata/tokens/emptyString.yaml diff --git a/cucumber-expressions/java/testdata/escapeCharIsStartIndexOfTextToken.yaml b/cucumber-expressions/java/testdata/tokens/escapeCharIsStartIndexOfTextToken.yaml similarity index 100% rename from cucumber-expressions/java/testdata/escapeCharIsStartIndexOfTextToken.yaml rename to cucumber-expressions/java/testdata/tokens/escapeCharIsStartIndexOfTextToken.yaml diff --git a/cucumber-expressions/java/testdata/escapeNonReservedCharacter.yaml b/cucumber-expressions/java/testdata/tokens/escapeNonReservedCharacter.yaml similarity index 100% rename from cucumber-expressions/java/testdata/escapeNonReservedCharacter.yaml rename to cucumber-expressions/java/testdata/tokens/escapeNonReservedCharacter.yaml diff --git a/cucumber-expressions/java/testdata/escapedAlternation.yaml b/cucumber-expressions/java/testdata/tokens/escapedAlternation.yaml similarity index 100% rename from cucumber-expressions/java/testdata/escapedAlternation.yaml rename to cucumber-expressions/java/testdata/tokens/escapedAlternation.yaml diff --git a/cucumber-expressions/java/testdata/escapedEndOfLine.yaml b/cucumber-expressions/java/testdata/tokens/escapedEndOfLine.yaml similarity index 100% rename from cucumber-expressions/java/testdata/escapedEndOfLine.yaml rename to cucumber-expressions/java/testdata/tokens/escapedEndOfLine.yaml diff --git a/cucumber-expressions/java/testdata/escapedOptional.yaml b/cucumber-expressions/java/testdata/tokens/escapedOptional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/escapedOptional.yaml rename to cucumber-expressions/java/testdata/tokens/escapedOptional.yaml diff --git a/cucumber-expressions/java/testdata/escapedParameter.yaml b/cucumber-expressions/java/testdata/tokens/escapedParameter.yaml similarity index 100% rename from cucumber-expressions/java/testdata/escapedParameter.yaml rename to cucumber-expressions/java/testdata/tokens/escapedParameter.yaml diff --git a/cucumber-expressions/java/testdata/escapedSpace.yaml b/cucumber-expressions/java/testdata/tokens/escapedSpace.yaml similarity index 100% rename from cucumber-expressions/java/testdata/escapedSpace.yaml rename to cucumber-expressions/java/testdata/tokens/escapedSpace.yaml diff --git a/cucumber-expressions/java/testdata/optional.yaml b/cucumber-expressions/java/testdata/tokens/optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/optional.yaml rename to cucumber-expressions/java/testdata/tokens/optional.yaml diff --git a/cucumber-expressions/java/testdata/optionalPhrase.yaml b/cucumber-expressions/java/testdata/tokens/optionalPhrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/optionalPhrase.yaml rename to cucumber-expressions/java/testdata/tokens/optionalPhrase.yaml diff --git a/cucumber-expressions/java/testdata/parameter.yaml b/cucumber-expressions/java/testdata/tokens/parameter.yaml similarity index 100% rename from cucumber-expressions/java/testdata/parameter.yaml rename to cucumber-expressions/java/testdata/tokens/parameter.yaml diff --git a/cucumber-expressions/java/testdata/parameterPhrase.yaml b/cucumber-expressions/java/testdata/tokens/parameterPhrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/parameterPhrase.yaml rename to cucumber-expressions/java/testdata/tokens/parameterPhrase.yaml diff --git a/cucumber-expressions/java/testdata/phrase.yaml b/cucumber-expressions/java/testdata/tokens/phrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/phrase.yaml rename to cucumber-expressions/java/testdata/tokens/phrase.yaml From 4b2cb5a1d9e188f6a6cfe3758f262c9e67327319 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 12:20:34 +0200 Subject: [PATCH 093/183] Clean up test --- .../CucumberExpressionTokenizerTest.java | 287 +----------------- .../cucumberexpressions/Expectation.java | 48 +++ .../FileToExpectationConverter.java | 31 ++ ...ionPhrase.yaml => alternation-phrase.yaml} | 0 .../{emptyString.yaml => empty-string.yaml} | 0 ...e-char-has-start-index-of-text-token.yaml} | 0 ...aml => escape-non-reserved-character.yaml} | 0 ...ernation.yaml => escaped-alternation.yaml} | 0 ...ndOfLine.yaml => escaped-end-of-line.yaml} | 0 ...pedOptional.yaml => escaped-optional.yaml} | 0 ...dParameter.yaml => escaped-parameter.yaml} | 0 .../{escapedSpace.yaml => escaped-space.yaml} | 0 ...tionalPhrase.yaml => optional-phrase.yaml} | 0 ...meterPhrase.yaml => parameter-phrase.yaml} | 0 14 files changed, 84 insertions(+), 282 deletions(-) create mode 100644 cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java create mode 100644 cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/FileToExpectationConverter.java rename cucumber-expressions/java/testdata/tokens/{alternationPhrase.yaml => alternation-phrase.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{emptyString.yaml => empty-string.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{escapeCharIsStartIndexOfTextToken.yaml => escape-char-has-start-index-of-text-token.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{escapeNonReservedCharacter.yaml => escape-non-reserved-character.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{escapedAlternation.yaml => escaped-alternation.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{escapedEndOfLine.yaml => escaped-end-of-line.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{escapedOptional.yaml => escaped-optional.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{escapedParameter.yaml => escaped-parameter.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{escapedSpace.yaml => escaped-space.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{optionalPhrase.yaml => optional-phrase.yaml} (100%) rename cucumber-expressions/java/testdata/tokens/{parameterPhrase.yaml => parameter-phrase.yaml} (100%) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 0b2ec8436d..c51b64506c 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -1,68 +1,33 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.Ast.Token; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.provider.MethodSource; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; import java.io.IOException; -import java.io.InputStream; import java.nio.file.DirectoryStream; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OF_LINE; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_OPTIONAL; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.START_OF_LINE; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.TEXT; -import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.singletonList; +import static java.nio.file.Files.newDirectoryStream; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; class CucumberExpressionTokenizerTest { private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); - private String displayName; - private static Stream test() throws IOException { - List expectations = new ArrayList<>(); - Path testdata = Paths.get("testdata/tokens"); - Yaml yaml = new Yaml(); - try (DirectoryStream stream = Files.newDirectoryStream(testdata)) { - for (Path path : stream) { - InputStream inputStream = Files.newInputStream(path); - Map map = yaml.loadAs(inputStream, Map.class); - Expectation expectation = new Expectation( - (String) map.get("expression"), - (List) map.get("tokens"), - (String) map.get("exception")); - expectations.add(expectation); - } - } - return expectations.stream(); + private static DirectoryStream test() throws IOException { + return newDirectoryStream(Paths.get("testdata", "tokens")) ; } @ParameterizedTest @MethodSource - void test(Expectation expectation) { + void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { if (expectation.getException() == null) { List tokens = tokenizer .tokenize(expectation.getExpression()) @@ -78,246 +43,4 @@ void test(Expectation expectation) { } } - @BeforeEach - void setup(TestInfo testInfo) { - String displayName = testInfo.getDisplayName(); - this.displayName = displayName.substring(0, displayName.length() - 2); - - } - - public static class Expectation { - String expression; - List tokens; - String exception; - - Expectation(String expression, List tokens, String exception) { - this.expression = expression; - this.tokens = tokens; - this.exception = exception; - } - - public String getExpression() { - return expression; - } - - public List getTokens() { - return tokens; - } - - public String getException() { - return exception; - } - - public void setExpression(String expression) { - this.expression = expression; - } - - public void setTokens(List tokens) { - this.tokens = tokens; - } - - public void setException(String exception) { - this.exception = exception; - } - - } - - private List tokenize(String s) { - List tokensString = null; - List tokens = null; - String t = null; - RuntimeException orig = null; - try { - tokens = tokenizer.tokenize(s); - tokensString = tokens.stream().map(Token::toString).collect(Collectors.toList()); - } catch (RuntimeException e) { - orig = e; - t = e.getMessage(); - } - - DumperOptions dumperOptions = new DumperOptions(); - String yaml = new Yaml(dumperOptions).dumpAsMap(new Expectation(s, tokensString, t)); - try { - Files.write(Paths.get("testdata", "tokens", displayName + ".yaml"), singletonList(yaml), UTF_8); - } catch (IOException e) { - throw new RuntimeException(e); - } - - if (orig != null) { - throw orig; - } - - return tokens; - } - - @Test - void emptyString() { - assertThat(tokenize(""), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("", END_OF_LINE, 0, 0) - )); - } - - @Test - void phrase() { - assertThat(tokenize("three blind mice"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("three", TEXT, 0, 5), - new Token(" ", WHITE_SPACE, 5, 6), - new Token("blind", TEXT, 6, 11), - new Token(" ", WHITE_SPACE, 11, 12), - new Token("mice", TEXT, 12, 16), - new Token("", END_OF_LINE, 16, 16) - )); - } - - @Test - void optional() { - assertThat(tokenize("(blind)"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("(", BEGIN_OPTIONAL, 0, 1), - new Token("blind", TEXT, 1, 6), - new Token(")", END_OPTIONAL, 6, 7), - new Token("", END_OF_LINE, 7, 7) - )); - } - - @Test - void escapedOptional() { - assertThat(tokenize("\\(blind\\)"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("(blind)", TEXT, 0, 9), - new Token("", END_OF_LINE, 9, 9) - )); - } - - @Test - void optionalPhrase() { - assertThat(tokenize("three (blind) mice"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("three", TEXT, 0, 5), - new Token(" ", WHITE_SPACE, 5, 6), - new Token("(", BEGIN_OPTIONAL, 6, 7), - new Token("blind", TEXT, 7, 12), - new Token(")", END_OPTIONAL, 12, 13), - new Token(" ", WHITE_SPACE, 13, 14), - new Token("mice", TEXT, 14, 18), - new Token("", END_OF_LINE, 18, 18) - )); - } - - @Test - void parameter() { - assertThat(tokenize("{string}"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("{", BEGIN_PARAMETER, 0, 1), - new Token("string", TEXT, 1, 7), - new Token("}", END_PARAMETER, 7, 8), - new Token("", END_OF_LINE, 8, 8) - )); - } - - @Test - void escapedParameter() { - assertThat(tokenize("\\{string\\}"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("{string}", TEXT, 0, 10), - new Token("", END_OF_LINE, 10, 10) - )); - } - - @Test - void parameterPhrase() { - assertThat(tokenize("three {string} mice"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("three", TEXT, 0, 5), - new Token(" ", WHITE_SPACE, 5, 6), - new Token("{", BEGIN_PARAMETER, 6, 7), - new Token("string", TEXT, 7, 13), - new Token("}", END_PARAMETER, 13, 14), - new Token(" ", WHITE_SPACE, 14, 15), - new Token("mice", TEXT, 15, 19), - new Token("", END_OF_LINE, 19, 19) - )); - } - - @Test - void alternation() { - assertThat(tokenize("blind/cripple"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("blind", TEXT, 0, 5), - new Token("/", ALTERNATION, 5, 6), - new Token("cripple", TEXT, 6, 13), - new Token("", END_OF_LINE, 13, 13) - )); - } - - @Test - void escapedAlternation() { - assertThat(tokenize("blind\\ and\\ famished\\/cripple mice"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("blind and famished/cripple", TEXT, 0, 29), - new Token(" ", WHITE_SPACE, 29, 30), - new Token("mice", TEXT, 30, 34), - new Token("", END_OF_LINE, 34, 34) - )); - } - - @Test - void escapeCharIsStartIndexOfTextToken() { - assertThat(tokenize(" \\/ "), contains( - new Token("", START_OF_LINE, 0, 0), - new Token(" ", WHITE_SPACE, 0, 1), - new Token("/", TEXT, 1, 3), - new Token(" ", WHITE_SPACE, 3, 4), - new Token("", END_OF_LINE, 4, 4) - )); - } - - @Test - void alternationPhrase() { - assertThat(tokenize("three blind/cripple mice"), contains( - new Token("", START_OF_LINE, 0, 0), - new Token("three", TEXT, 0, 5), - new Token(" ", WHITE_SPACE, 5, 6), - new Token("blind", TEXT, 6, 11), - new Token("/", ALTERNATION, 11, 12), - new Token("cripple", TEXT, 12, 19), - new Token(" ", WHITE_SPACE, 19, 20), - new Token("mice", TEXT, 20, 24), - new Token("", END_OF_LINE, 24, 24) - )); - } - - @Test - void escapedSpace() { - assertThat(tokenize("\\ "), contains( - new Token("", START_OF_LINE, 0, 0), - new Token(" ", TEXT, 0, 2), - new Token("", END_OF_LINE, 2, 2) - )); - } - - @Test - void escapedEndOfLine() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> tokenize("\\")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + - "\n" + - "\\\n" + - " ^\n" + - "The end of line can not be escaped.\n" + - "You can use '\\\\' to escape the the '\\'")); - } - - @Test - void escapeNonReservedCharacter() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> tokenize("\\[")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 2:\n" + - "\n" + - "\\[\n" + - " ^\n" + - "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped.\n" + - "If you did mean to use an '\\' you can use '\\\\' to escape it")); - } - } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java new file mode 100644 index 0000000000..ce0b016dfa --- /dev/null +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java @@ -0,0 +1,48 @@ +package io.cucumber.cucumberexpressions; + +import java.util.List; +import java.util.Map; + +class Expectation { + String expression; + List tokens; + String exception; + + Expectation(String expression, List tokens, String exception) { + this.expression = expression; + this.tokens = tokens; + this.exception = exception; + } + + public String getExpression() { + return expression; + } + + public List getTokens() { + return tokens; + } + + public String getException() { + return exception; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + public void setTokens(List tokens) { + this.tokens = tokens; + } + + public void setException(String exception) { + this.exception = exception; + } + + public static Expectation fromMap(Map map) { + return new Expectation( + (String) map.get("expression"), + (List) map.get("tokens"), + (String) map.get("exception")); + } + +} diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/FileToExpectationConverter.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/FileToExpectationConverter.java new file mode 100644 index 0000000000..53999faf58 --- /dev/null +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/FileToExpectationConverter.java @@ -0,0 +1,31 @@ +package io.cucumber.cucumberexpressions; + +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Map; + +import static io.cucumber.cucumberexpressions.Expectation.fromMap; +import static java.nio.file.Files.newInputStream; + +class FileToExpectationConverter implements ArgumentConverter { + Yaml yaml = new Yaml(); + + @Override + public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + try { + Path path = (Path) source; + InputStream inputStream = newInputStream(path); + Map map = yaml.loadAs(inputStream, Map.class); + return fromMap(map); + } catch (IOException e) { + throw new ArgumentConversionException("Could not load " + source, e); + } + } + +} diff --git a/cucumber-expressions/java/testdata/tokens/alternationPhrase.yaml b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/alternationPhrase.yaml rename to cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml diff --git a/cucumber-expressions/java/testdata/tokens/emptyString.yaml b/cucumber-expressions/java/testdata/tokens/empty-string.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/emptyString.yaml rename to cucumber-expressions/java/testdata/tokens/empty-string.yaml diff --git a/cucumber-expressions/java/testdata/tokens/escapeCharIsStartIndexOfTextToken.yaml b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/escapeCharIsStartIndexOfTextToken.yaml rename to cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml diff --git a/cucumber-expressions/java/testdata/tokens/escapeNonReservedCharacter.yaml b/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/escapeNonReservedCharacter.yaml rename to cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml diff --git a/cucumber-expressions/java/testdata/tokens/escapedAlternation.yaml b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/escapedAlternation.yaml rename to cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml diff --git a/cucumber-expressions/java/testdata/tokens/escapedEndOfLine.yaml b/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/escapedEndOfLine.yaml rename to cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml diff --git a/cucumber-expressions/java/testdata/tokens/escapedOptional.yaml b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/escapedOptional.yaml rename to cucumber-expressions/java/testdata/tokens/escaped-optional.yaml diff --git a/cucumber-expressions/java/testdata/tokens/escapedParameter.yaml b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/escapedParameter.yaml rename to cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml diff --git a/cucumber-expressions/java/testdata/tokens/escapedSpace.yaml b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/escapedSpace.yaml rename to cucumber-expressions/java/testdata/tokens/escaped-space.yaml diff --git a/cucumber-expressions/java/testdata/tokens/optionalPhrase.yaml b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/optionalPhrase.yaml rename to cucumber-expressions/java/testdata/tokens/optional-phrase.yaml diff --git a/cucumber-expressions/java/testdata/tokens/parameterPhrase.yaml b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/parameterPhrase.yaml rename to cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml From 8d1dc0cf7e8272842650fefbbb03470e573ae4f2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 12:26:42 +0200 Subject: [PATCH 094/183] Split composite tests --- .../CucumberExpressionTest.java | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 4d54af1d3b..100df90b9a 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -42,21 +42,37 @@ public void matches_alternation() { } @Test - public void matches_optional_before_alternation() { + public void matches_optional_before_alternation_1() { assertEquals(emptyList(), match("three (brown )mice/rats", "three brown mice")); + } + + @Test + public void matches_optional_before_alternation_2() { assertEquals(emptyList(), match("three (brown )mice/rats", "three rats")); } @Test - public void matches_optional_in_alternation() { + public void matches_optional_in_alternation_1() { assertEquals(singletonList(3), match("{int} rat(s)/mouse/mice", "3 rats")); + } + + @Test + public void matches_optional_in_alternation_2() { assertEquals(singletonList(2), match("{int} rat(s)/mouse/mice", "2 mice")); + } + + @Test + public void matches_optional_in_alternation_3() { assertEquals(singletonList(1), match("{int} rat(s)/mouse/mice", "1 mouse")); } @Test - public void matches_optional_before_alternation_with_regex_characters() { + public void matches_optional_before_alternation_with_regex_characters_1() { assertEquals(singletonList(2), match("I wait {int} second(s)./second(s)?", "I wait 2 seconds?")); + } + + @Test + public void matches_optional_before_alternation_with_regex_characters_2() { assertEquals(singletonList(1), match("I wait {int} second(s)./second(s)?", "I wait 1 second.")); } @@ -207,7 +223,6 @@ public void matches_double_quoted_empty_string_as_empty_string_along_with_other_ match("three {string} and {string} mice", "three \"\" and \"handsome\" mice")); } - @Test public void alternation_seperator_can_be_used_in_parameter() { parameterTypeRegistry @@ -217,13 +232,25 @@ public void alternation_seperator_can_be_used_in_parameter() { } @Test - public void matches_escaped_parenthesis() { + public void matches_escaped_parenthesis_1() { assertEquals(emptyList(), match("three \\(exceptionally) \\{string} mice", "three (exceptionally) {string} mice")); + } + + @Test + public void matches_escaped_parenthesis_2() { assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); + } + + @Test + public void matches_escaped_parenthesis_3() { assertEquals(singletonList("blind"), match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); + } + + @Test + public void matches_escaped_parenthesis_4() { parameterTypeRegistry .defineParameterType(new ParameterType<>("{string}", "\"(.*)\"", String.class, (String arg) -> arg)); assertEquals(singletonList("blind"), @@ -242,8 +269,12 @@ public void matches_escaped_slash() { } @Test - public void matches_doubly_escaped_slash() { + public void matches_doubly_escaped_slash_1() { assertEquals(emptyList(), match("12\\\\/2020", "12\\")); + } + + @Test + public void matches_doubly_escaped_slash_2() { assertEquals(emptyList(), match("12\\\\/2020", "2020")); } @@ -258,8 +289,12 @@ public void doesnt_match_float_as_int() { } @Test - public void matches_float() { + public void matches_float_1() { assertEquals(singletonList(0.22f), match("{float}", "0.22")); + } + + @Test + public void matches_float_2() { assertEquals(singletonList(0.22f), match("{float}", ".22")); } @@ -315,8 +350,11 @@ public void allows_escaped_optional_parameter_types() { } @Test - public void allows_parameter_type_in_alternation() { + public void allows_parameter_type_in_alternation_1() { assertEquals(singletonList(18), match("a/i{int}n/y", "i18n")); + } + @Test + public void allows_parameter_type_in_alternation_2() { assertEquals(singletonList(11), match("a/i{int}n/y", "a11y")); } From 10809352b2b84c84aa8dfb8bdd6b2f35ed7e47ed Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 12:27:53 +0200 Subject: [PATCH 095/183] Clean up --- .../io/cucumber/cucumberexpressions/Expectation.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java index ce0b016dfa..c0ae4cf339 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java @@ -26,18 +26,6 @@ public String getException() { return exception; } - public void setExpression(String expression) { - this.expression = expression; - } - - public void setTokens(List tokens) { - this.tokens = tokens; - } - - public void setException(String exception) { - this.exception = exception; - } - public static Expectation fromMap(Map map) { return new Expectation( (String) map.get("expression"), From 4c06f74a12fb6c8d29642e81d295e1daadcbcaba Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 20:52:07 +0200 Subject: [PATCH 096/183] Rename tokens to elements --- .../CucumberExpressionTokenizerTest.java | 2 +- .../io/cucumber/cucumberexpressions/Expectation.java | 12 ++++++------ .../java/testdata/tokens/alternation-phrase.yaml | 2 +- .../java/testdata/tokens/alternation.yaml | 2 +- .../java/testdata/tokens/empty-string.yaml | 2 +- .../escape-char-has-start-index-of-text-token.yaml | 2 +- .../tokens/escape-non-reserved-character.yaml | 2 +- .../java/testdata/tokens/escaped-alternation.yaml | 2 +- .../java/testdata/tokens/escaped-end-of-line.yaml | 2 +- .../java/testdata/tokens/escaped-optional.yaml | 2 +- .../java/testdata/tokens/escaped-parameter.yaml | 2 +- .../java/testdata/tokens/escaped-space.yaml | 2 +- .../java/testdata/tokens/optional-phrase.yaml | 2 +- .../java/testdata/tokens/optional.yaml | 2 +- .../java/testdata/tokens/parameter-phrase.yaml | 2 +- .../java/testdata/tokens/parameter.yaml | 2 +- .../java/testdata/tokens/phrase.yaml | 2 +- 17 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index c51b64506c..dc3f8e4d87 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -34,7 +34,7 @@ void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation .stream() .map(Token::toString) .collect(Collectors.toList()); - assertThat(tokens, is(expectation.getTokens())); + assertThat(tokens, is(expectation.getElements())); } else { CucumberExpressionException exception = assertThrows( CucumberExpressionException.class, diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java index c0ae4cf339..c7cb94ba76 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java @@ -5,12 +5,12 @@ class Expectation { String expression; - List tokens; + List elements; String exception; - Expectation(String expression, List tokens, String exception) { + Expectation(String expression, List elements, String exception) { this.expression = expression; - this.tokens = tokens; + this.elements = elements; this.exception = exception; } @@ -18,8 +18,8 @@ public String getExpression() { return expression; } - public List getTokens() { - return tokens; + public List getElements() { + return elements; } public String getException() { @@ -29,7 +29,7 @@ public String getException() { public static Expectation fromMap(Map map) { return new Expectation( (String) map.get("expression"), - (List) map.get("tokens"), + (List) map.get("elements"), (String) map.get("exception")); } diff --git a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml index 3bc7ce0c43..53b3ef2f8e 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml @@ -1,6 +1,6 @@ exception: null expression: three blind/cripple mice -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=5, text='three'] - Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] diff --git a/cucumber-expressions/java/testdata/tokens/alternation.yaml b/cucumber-expressions/java/testdata/tokens/alternation.yaml index e2a0ba1a0a..1f9c5b4c61 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation.yaml @@ -1,6 +1,6 @@ exception: null expression: blind/cripple -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=5, text='blind'] - Token[type=ALTERNATION, startIndex=5, endIndex=6, text='/'] diff --git a/cucumber-expressions/java/testdata/tokens/empty-string.yaml b/cucumber-expressions/java/testdata/tokens/empty-string.yaml index e35f03d290..86fedfa01e 100644 --- a/cucumber-expressions/java/testdata/tokens/empty-string.yaml +++ b/cucumber-expressions/java/testdata/tokens/empty-string.yaml @@ -1,6 +1,6 @@ exception: null expression: '' -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=END_OF_LINE, startIndex=0, endIndex=0, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml index 0f735266e9..4017884cc2 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml @@ -1,6 +1,6 @@ exception: null expression: ' \/ ' -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=WHITE_SPACE, startIndex=0, endIndex=1, text=' '] - Token[type=TEXT, startIndex=1, endIndex=3, text='/'] diff --git a/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml index a1f3a9456d..4a136b8422 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml @@ -6,5 +6,5 @@ exception: |- Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. If you did mean to use an '\' you can use '\\' to escape it expression: \[ -tokens: null +elements: null diff --git a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml index dd2d61e0d7..35d49bd4e9 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml @@ -1,6 +1,6 @@ exception: null expression: blind\ and\ famished\/cripple mice -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=29, text='blind and famished/cripple'] - Token[type=WHITE_SPACE, startIndex=29, endIndex=30, text=' '] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml index ef6039ef02..d625886802 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml @@ -6,5 +6,5 @@ exception: |- The end of line can not be escaped. You can use '\\' to escape the the '\' expression: \ -tokens: null +elements: null diff --git a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml index 6c044516e9..cc916a885b 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml @@ -1,6 +1,6 @@ exception: null expression: \(blind\) -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=9, text='(blind)'] - Token[type=END_OF_LINE, startIndex=9, endIndex=9, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml index 4ca42d0f4a..bba974abec 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml @@ -1,6 +1,6 @@ exception: null expression: \{string\} -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=10, text='{string}'] - Token[type=END_OF_LINE, startIndex=10, endIndex=10, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml index fe74dfa361..8c13fbe951 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml @@ -1,6 +1,6 @@ exception: null expression: '\ ' -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=2, text=' '] - Token[type=END_OF_LINE, startIndex=2, endIndex=2, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml index 226a133e03..b3ed6e3ac8 100644 --- a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml @@ -1,6 +1,6 @@ exception: null expression: three (blind) mice -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=5, text='three'] - Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] diff --git a/cucumber-expressions/java/testdata/tokens/optional.yaml b/cucumber-expressions/java/testdata/tokens/optional.yaml index 1be2c01591..949eaf9028 100644 --- a/cucumber-expressions/java/testdata/tokens/optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional.yaml @@ -1,6 +1,6 @@ exception: null expression: (blind) -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=BEGIN_OPTIONAL, startIndex=0, endIndex=1, text='('] - Token[type=TEXT, startIndex=1, endIndex=6, text='blind'] diff --git a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml index 1fc21800f0..aa931293c0 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml @@ -1,6 +1,6 @@ exception: null expression: three {string} mice -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=5, text='three'] - Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] diff --git a/cucumber-expressions/java/testdata/tokens/parameter.yaml b/cucumber-expressions/java/testdata/tokens/parameter.yaml index f9d7a83ee0..cca7b599b3 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter.yaml @@ -1,6 +1,6 @@ exception: null expression: '{string}' -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=BEGIN_PARAMETER, startIndex=0, endIndex=1, text='{'] - Token[type=TEXT, startIndex=1, endIndex=7, text='string'] diff --git a/cucumber-expressions/java/testdata/tokens/phrase.yaml b/cucumber-expressions/java/testdata/tokens/phrase.yaml index 44359d7d03..5f1168370a 100644 --- a/cucumber-expressions/java/testdata/tokens/phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/phrase.yaml @@ -1,6 +1,6 @@ exception: null expression: three blind mice -tokens: +elements: - Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] - Token[type=TEXT, startIndex=0, endIndex=5, text='three'] - Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] From 9ca3c742ccfb5f970a51a37737d726fd926f127c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 21:27:57 +0200 Subject: [PATCH 097/183] More self generation --- .../io/cucumber/cucumberexpressions/Ast.java | 20 +- .../CucumberExpressionParserTest.java | 601 ++++++++++-------- .../cucumberexpressions/Expectation.java | 33 +- .../testdata/tokens/alternation-phrase.yaml | 18 +- .../java/testdata/tokens/alternation.yaml | 10 +- .../java/testdata/tokens/empty-string.yaml | 4 +- ...pe-char-has-start-index-of-text-token.yaml | 10 +- .../testdata/tokens/escaped-alternation.yaml | 10 +- .../testdata/tokens/escaped-optional.yaml | 6 +- .../testdata/tokens/escaped-parameter.yaml | 6 +- .../java/testdata/tokens/escaped-space.yaml | 6 +- .../java/testdata/tokens/optional-phrase.yaml | 18 +- .../java/testdata/tokens/optional.yaml | 10 +- .../testdata/tokens/parameter-phrase.yaml | 18 +- .../java/testdata/tokens/parameter.yaml | 10 +- .../java/testdata/tokens/phrase.yaml | 14 +- 16 files changed, 450 insertions(+), 344 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index ec8db1c09f..d1a72fc933 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -95,26 +95,32 @@ public String toString() { private StringBuilder toString(int depth) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < depth; i++) { - sb.append("\t"); + sb.append(" "); } - sb.append("AstNode{").append(start).append(":").append(end).append(", type=").append(type); + sb.append("AstNode[") + .append("type=").append(type) + .append("', start='") + .append(start) + .append("', end=") + .append(end) + .append("'"); if (token != null) { sb.append(", token=").append(token); } - if (nodes != null) { + if (nodes != null && !nodes.isEmpty()) { sb.append("\n"); for (Node node : nodes) { sb.append(node.toString(depth + 1)); sb.append("\n"); } for (int i = 0; i < depth; i++) { - sb.append("\t"); + sb.append(" "); } } - sb.append('}'); + sb.append(']'); return sb; } @@ -222,8 +228,8 @@ public int hashCode() { public String toString() { return new StringJoiner(", ", Token.class.getSimpleName() + "[", "]") .add("type=" + type) - .add("startIndex=" + start) - .add("endIndex=" + end) + .add("start=" + start) + .add("end=" + end) .add("text='" + text + "'") .toString(); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index eadb7a2e28..2618189581 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -1,7 +1,25 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.Ast.Node; +import io.cucumber.cucumberexpressions.Ast.Token; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.MethodSource; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATION_NODE; import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATIVE_NODE; @@ -9,11 +27,12 @@ import static io.cucumber.cucumberexpressions.Ast.Node.Type.OPTIONAL_NODE; import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE; +import static java.nio.file.Files.newDirectoryStream; +import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; class CucumberExpressionParserTest { @@ -70,297 +89,347 @@ void anonymousParameter() { )); } + @Test + void optionalPhrase() { + assertThat(astOf("three (blind) mice"), equalTo( + new Node(EXPRESSION_NODE, 0, 18, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(OPTIONAL_NODE, 6, 13, + new Node(TEXT_NODE, 7, 12, "blind") + ), + new Node(TEXT_NODE, 13, 14, " "), + new Node(TEXT_NODE, 14, 18, "mice") + ) + )); + } - @Test - void optionalPhrase() { - assertThat(astOf("three (blind) mice"), equalTo( - new Node(EXPRESSION_NODE,0,18, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(OPTIONAL_NODE, 6, 13, - new Node(TEXT_NODE, 7, 12, "blind") - ), - new Node(TEXT_NODE, 13, 14, " "), - new Node(TEXT_NODE, 14, 18, "mice") - ) - )); - } + @Test + void escapedBackSlash() { + assertThat(astOf("\\\\"), equalTo( + new Node(EXPRESSION_NODE, 0, 2, + new Node(TEXT_NODE, 0, 2, "\\") + ) + )); + } - @Test - void escapedBackSlash() { - assertThat(astOf("\\\\"), equalTo( - new Node(EXPRESSION_NODE,0,2, - new Node(TEXT_NODE, 0, 2, "\\") - ) - )); - } + @Test + void openingBrace() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + + "\n" + + "{\n" + + "^\n" + + "The '{' does not have a matching '}'.\n" + + "If you did not intend to use a parameter you can use '\\{' to escape the a parameter" + )); + } - @Test - void openingBrace() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + - "\n" + - "{\n" + - "^\n" + - "The '{' does not have a matching '}'.\n" + - "If you did not intend to use a parameter you can use '\\{' to escape the a parameter" - )); - } + @Test + void closingBrace() { + assertThat(astOf("}"), equalTo( + new Node(EXPRESSION_NODE, 0, 1, + new Node(TEXT_NODE, 0, 1, "}") + ) + )); + } - @Test - void closingBrace() { - assertThat(astOf("}"), equalTo( - new Node(EXPRESSION_NODE,0,1, - new Node(TEXT_NODE, 0, 1, "}") - ) - )); - } + @Test + void unfinishedParameter() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + + "\n" + + "{string\n" + + "^\n" + + "The '{' does not have a matching '}'.\n" + + "If you did not intend to use a parameter you can use '\\{' to escape the a parameter")); + } - @Test - void unfinishedParameter() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + - "\n" + - "{string\n" + - "^\n" + - "The '{' does not have a matching '}'.\n" + - "If you did not intend to use a parameter you can use '\\{' to escape the a parameter")); - } + @Test + void openingParenthesis() { + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); + assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + + "\n" + + "(\n" + + "^\n" + + "The '(' does not have a matching ')'.\n" + + "If you did not intend to use optional text you can use '\\(' to escape the optional text" + )); + } - @Test - void openingParenthesis() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + - "\n" + - "(\n" + - "^\n" + - "The '(' does not have a matching ')'.\n" + - "If you did not intend to use optional text you can use '\\(' to escape the optional text" - )); - } + @Test + void closingParenthesis() { + assertThat(astOf(")"), equalTo( + new Node(EXPRESSION_NODE, 0, 1, + new Node(TEXT_NODE, 0, 1, ")") + ) + )); + } - @Test - void closingParenthesis() { - assertThat(astOf(")"), equalTo( - new Node(EXPRESSION_NODE,0,1, - new Node(TEXT_NODE, 0, 1, ")") - ) - )); - } + @Test + void escapedOpeningParenthesis() { + assertThat(astOf("\\("), equalTo( + new Node(EXPRESSION_NODE, 0, 2, + new Node(TEXT_NODE, 0, 2, "(") + ) + )); + } - @Test - void escapedOpeningParenthesis() { - assertThat(astOf("\\("), equalTo( - new Node(EXPRESSION_NODE,0,2, - new Node(TEXT_NODE, 0, 2, "(") - ) - )); - } + @Test + void escapedOptional() { + assertThat(astOf("\\(blind)"), equalTo( + new Node(EXPRESSION_NODE, 0, 8, + new Node(TEXT_NODE, 0, 7, "(blind"), + new Node(TEXT_NODE, 7, 8, ")") + ) + )); + } - @Test - void escapedOptional() { - assertThat(astOf("\\(blind)"), equalTo( - new Node(EXPRESSION_NODE,0,8, - new Node(TEXT_NODE, 0, 7, "(blind"), - new Node(TEXT_NODE, 7, 8, ")") - ) - )); - } + @Test + void escapedOptionalPhrase() { + assertThat(astOf("three \\(blind) mice"), equalTo( + new Node(EXPRESSION_NODE, 0, 19, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(TEXT_NODE, 6, 13, "(blind"), + new Node(TEXT_NODE, 13, 14, ")"), + new Node(TEXT_NODE, 14, 15, " "), + new Node(TEXT_NODE, 15, 19, "mice") + ) + )); + } - @Test - void escapedOptionalPhrase() { - assertThat(astOf("three \\(blind) mice"), equalTo( - new Node(EXPRESSION_NODE,0,19, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(TEXT_NODE, 6, 13, "(blind"), - new Node(TEXT_NODE, 13, 14, ")"), - new Node(TEXT_NODE, 14, 15, " "), - new Node(TEXT_NODE, 15, 19, "mice") - ) - )); - } + @Test + void escapedOptionalFollowedByOptional() { + assertThat(astOf("three \\((very) blind) mice"), equalTo( + new Node(EXPRESSION_NODE, 0, 26, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(TEXT_NODE, 6, 8, "("), + new Node(OPTIONAL_NODE, 8, 14, + new Node(TEXT_NODE, 9, 13, "very") + ), + new Node(TEXT_NODE, 14, 15, " "), + new Node(TEXT_NODE, 15, 20, "blind"), + new Node(TEXT_NODE, 20, 21, ")"), + new Node(TEXT_NODE, 21, 22, " "), + new Node(TEXT_NODE, 22, 26, "mice") + ) + )); + } - @Test - void escapedOptionalFollowedByOptional() { - assertThat(astOf("three \\((very) blind) mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 26, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(TEXT_NODE, 6, 8, "("), - new Node(OPTIONAL_NODE, 8, 14, - new Node(TEXT_NODE, 9, 13, "very") - ), - new Node(TEXT_NODE, 14, 15, " "), - new Node(TEXT_NODE, 15, 20, "blind"), - new Node(TEXT_NODE, 20, 21, ")"), - new Node(TEXT_NODE, 21, 22, " "), - new Node(TEXT_NODE, 22, 26, "mice") - ) - )); - } + @Test + void optionalContainingEscapedOptional() { + assertThat(astOf("three ((very\\) blind) mice"), equalTo( + new Node(EXPRESSION_NODE, 0, 26, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(OPTIONAL_NODE, 6, 21, + new Node(TEXT_NODE, 7, 8, "("), + new Node(TEXT_NODE, 8, 14, "very)"), + new Node(TEXT_NODE, 14, 15, " "), + new Node(TEXT_NODE, 15, 20, "blind") + ), + new Node(TEXT_NODE, 21, 22, " "), + new Node(TEXT_NODE, 22, 26, "mice") + ) + )); + } - @Test - void optionalContainingEscapedOptional() { - assertThat(astOf("three ((very\\) blind) mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 26, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(OPTIONAL_NODE, 6,21, - new Node(TEXT_NODE, 7, 8, "("), - new Node(TEXT_NODE, 8, 14, "very)"), - new Node(TEXT_NODE, 14, 15, " "), - new Node(TEXT_NODE, 15, 20, "blind") - ), - new Node(TEXT_NODE, 21, 22, " "), - new Node(TEXT_NODE, 22, 26, "mice") - ) - )); - } + @Test + void alternation() { + assertThat(astOf("mice/rats"), equalTo( + new Node(EXPRESSION_NODE, 0, 9, + new Node(ALTERNATION_NODE, 0, 9, + new Node(ALTERNATIVE_NODE, 0, 4, + new Node(TEXT_NODE, 0, 4, "mice") + ), + new Node(ALTERNATIVE_NODE, 5, 9, + new Node(TEXT_NODE, 5, 9, "rats") + ) + ) + ) + )); + } - @Test - void alternation() { - assertThat(astOf("mice/rats"), equalTo( - new Node(EXPRESSION_NODE, 0,9, - new Node(ALTERNATION_NODE, 0,9, - new Node(ALTERNATIVE_NODE,0,4, - new Node(TEXT_NODE, 0, 4, "mice") - ), - new Node(ALTERNATIVE_NODE, 5,9, - new Node(TEXT_NODE, 5, 9, "rats") - ) - ) - ) - )); - } + @Test + void emptyAlternation() { + assertThat(astOf("/"), equalTo( + new Node(EXPRESSION_NODE, 0, 1, + new Node(ALTERNATION_NODE, 0, 1, + new Node(ALTERNATIVE_NODE, 0, 0), + new Node(ALTERNATIVE_NODE, 1, 1) + ) + ) + )); + } - @Test - void emptyAlternation() { - assertThat(astOf("/"), equalTo( - new Node(EXPRESSION_NODE,0,1, - new Node(ALTERNATION_NODE,0,1, - new Node(ALTERNATIVE_NODE, 0,0), - new Node(ALTERNATIVE_NODE, 1,1) - ) - ) - )); - } + @Test + void emptyAlternations() { + assertThat(astOf("//"), equalTo( + new Node(EXPRESSION_NODE, 0, 2, + new Node(ALTERNATION_NODE, 0, 2, + new Node(ALTERNATIVE_NODE, 0, 0), + new Node(ALTERNATIVE_NODE, 1, 1), + new Node(ALTERNATIVE_NODE, 2, 2) + ) + ) + )); + } - @Test - void emptyAlternations() { - assertThat(astOf("//"), equalTo( - new Node(EXPRESSION_NODE,0,2, - new Node(ALTERNATION_NODE, 0,2, - new Node(ALTERNATIVE_NODE, 0,0), - new Node(ALTERNATIVE_NODE, 1,1), - new Node(ALTERNATIVE_NODE, 2,2) - ) - ) - )); - } + @Test + void escapedAlternation() { + assertThat(astOf("mice\\/rats"), equalTo( + new Node(EXPRESSION_NODE, 0, 10, + new Node(TEXT_NODE, 0, 10, "mice/rats") + ) + )); + } - @Test - void escapedAlternation() { - assertThat(astOf("mice\\/rats"), equalTo( - new Node(EXPRESSION_NODE, 0, 10, - new Node(TEXT_NODE, 0, 10, "mice/rats") - ) - )); - } + @Test + void alternationPhrase() { + assertThat(astOf("three hungry/blind mice"), equalTo( + new Node(EXPRESSION_NODE, 0, 23, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(ALTERNATION_NODE, 6, 18, + new Node(ALTERNATIVE_NODE, 6, 12, + new Node(TEXT_NODE, 6, 12, "hungry") + ), + new Node(ALTERNATIVE_NODE, 13, 18, + new Node(TEXT_NODE, 13, 18, "blind") + ) + ), + new Node(TEXT_NODE, 18, 19, " "), + new Node(TEXT_NODE, 19, 23, "mice") + ) + )); + } + + @Test + void alternationWithWhiteSpace() { + assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( + new Node(EXPRESSION_NODE, 0, 29, + new Node(ALTERNATION_NODE, 0, 29, + new Node(ALTERNATIVE_NODE, 0, 15, + new Node(TEXT_NODE, 0, 15, " three hungry") + ), + new Node(ALTERNATIVE_NODE, 16, 29, + new Node(TEXT_NODE, 16, 29, "blind mice ") + ) + ) + + ) + )); + } - @Test - void alternationPhrase() { - assertThat(astOf("three hungry/blind mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 23, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(ALTERNATION_NODE, 6,18, - new Node(ALTERNATIVE_NODE, 6, 12, - new Node(TEXT_NODE, 6, 12, "hungry") - ), - new Node(ALTERNATIVE_NODE, 13, 18, - new Node(TEXT_NODE, 13, 18, "blind") - ) - ), - new Node(TEXT_NODE, 18, 19, " "), - new Node(TEXT_NODE, 19, 23, "mice") - ) - )); + @Test + void alternationWithUnusedEndOptional() { + assertThat(astOf("three )blind\\ mice/rats"), equalTo( + new Node(EXPRESSION_NODE, 0, 23, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(ALTERNATION_NODE, 6, 23, + new Node(ALTERNATIVE_NODE, 6, 18, + new Node(TEXT_NODE, 6, 7, ")"), + new Node(TEXT_NODE, 7, 18, "blind mice") + ), + new Node(ALTERNATIVE_NODE, 19, 23, + new Node(TEXT_NODE, 19, 23, "rats") + ) + ) + ) + )); + } + + @Test + void alternationWithUnusedStartOptional() { + CucumberExpressionException exception = assertThrows( + CucumberExpressionException.class, + () -> astOf("three blind\\ mice/rats(")); + assertThat(exception.getMessage(), is("" + + "This Cucumber Expression has a problem at column 23:\n" + + "\n" + + "three blind\\ mice/rats(\n" + + " ^\n" + + "The '(' does not have a matching ')'.\n" + + "If you did not intend to use optional text you can use '\\(' to escape the optional text")); + } + + @Test + void alternationFollowedByOptional() { + assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( + new Node(EXPRESSION_NODE, 0, 23, + new Node(TEXT_NODE, 0, 5, "three"), + new Node(TEXT_NODE, 5, 6, " "), + new Node(ALTERNATION_NODE, 6, 23, + new Node(ALTERNATIVE_NODE, 6, 16, + new Node(TEXT_NODE, 6, 16, "blind rat") + ), + new Node(ALTERNATIVE_NODE, 17, 23, + new Node(TEXT_NODE, 17, 20, "cat"), + new Node(OPTIONAL_NODE, 20, 23, + new Node(TEXT_NODE, 21, 22, "s") + ) + ) + ) + ) + )); + } + + String displayName; + + @BeforeEach + public void before(TestInfo testInfo){ + displayName = testInfo.getTestMethod().get().getName(); + } + + private Node astOf(String expression) { + Node parse = null; + RuntimeException runtimeException = null; + String message = null; + try { + parse = parser.parse(expression); + } catch (RuntimeException e) { + runtimeException = e; + message = e.getMessage(); } - @Test - void alternationWithWhiteSpace() { - assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( - new Node(EXPRESSION_NODE, 0, 29, - new Node(ALTERNATION_NODE, 0,29, - new Node(ALTERNATIVE_NODE,0,15, - new Node(TEXT_NODE, 0, 15, " three hungry") - ), - new Node(ALTERNATIVE_NODE,16,29, - new Node(TEXT_NODE, 16, 29, "blind mice ") - ) - ) - - ) - )); + Expectation expectation = new Expectation(expression, parse != null ? parse.toString() : null, message); + + Yaml yaml = new Yaml(); + String s = yaml.dumpAsMap(expectation); + + try { + Files.write(Paths.get("testdata", "ast", displayName + ".yaml" ), singletonList(s), StandardCharsets.UTF_8 ); + } catch (IOException e) { + throw new RuntimeException(e); } - @Test - void alternationWithUnusedEndOptional() { - assertThat(astOf("three )blind\\ mice/rats"), equalTo( - new Node(EXPRESSION_NODE,0,23, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(ALTERNATION_NODE,6,23, - new Node(ALTERNATIVE_NODE,6,18, - new Node(TEXT_NODE, 6, 7, ")"), - new Node(TEXT_NODE, 7, 18, "blind mice") - ), - new Node(ALTERNATIVE_NODE,19,23, - new Node(TEXT_NODE, 19, 23, "rats") - ) - ) - ) - )); + if (runtimeException != null) { + throw runtimeException; } + return parse; + } - @Test - void alternationWithUnusedStartOptional() { + + + private static DirectoryStream test() throws IOException { + return newDirectoryStream(Paths.get("testdata", "ast")) ; + } + + @ParameterizedTest + @MethodSource + void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { + if (expectation.getException() == null) { + Node node = parser.parse(expectation.getExpression()); + assertThat(node.toString(), is(expectation.getElement())); + } else { CucumberExpressionException exception = assertThrows( CucumberExpressionException.class, - () -> astOf("three blind\\ mice/rats(")); - assertThat(exception.getMessage(), is("" + - "This Cucumber Expression has a problem at column 23:\n" + - "\n" + - "three blind\\ mice/rats(\n" + - " ^\n" + - "The '(' does not have a matching ')'.\n" + - "If you did not intend to use optional text you can use '\\(' to escape the optional text")); + () -> parser.parse(expectation.getExpression())); + assertThat(exception.getMessage(), is(exception.getMessage())); } - - @Test - void alternationFollowedByOptional() { - assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( - new Node(EXPRESSION_NODE,0,23, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(ALTERNATION_NODE,6,23, - new Node(ALTERNATIVE_NODE,6,16, - new Node(TEXT_NODE, 6, 16, "blind rat") - ), - new Node(ALTERNATIVE_NODE,17,23, - new Node(TEXT_NODE, 17, 20, "cat"), - new Node(OPTIONAL_NODE,20,23, - new Node(TEXT_NODE, 21, 22, "s") - ) - ) - ) - ) - )); - } - - private Node astOf(String expression) { - return parser.parse(expression); } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java index c7cb94ba76..2ea93656d9 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java @@ -5,15 +5,25 @@ class Expectation { String expression; + String element; List elements; String exception; - Expectation(String expression, List elements, String exception) { + private Expectation(String expression, String element, List elements, String exception) { this.expression = expression; + this.element = element; this.elements = elements; this.exception = exception; } + Expectation(String expression, List elements, String exception) { + this(expression, null, elements, exception); + } + + Expectation(String expression, String element, String exception) { + this(expression, element, null, exception); + } + public String getExpression() { return expression; } @@ -29,8 +39,29 @@ public String getException() { public static Expectation fromMap(Map map) { return new Expectation( (String) map.get("expression"), + (String) map.get("element"), (List) map.get("elements"), (String) map.get("exception")); } + public void setExpression(String expression) { + this.expression = expression; + } + + public void setElements(List elements) { + this.elements = elements; + } + + public void setException(String exception) { + this.exception = exception; + } + + public String getElement() { + return element; + } + + public void setElement(String element) { + this.element = element; + } + } diff --git a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml index 53b3ef2f8e..c2bb640771 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml @@ -1,13 +1,13 @@ exception: null expression: three blind/cripple mice elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=5, text='three'] -- Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] -- Token[type=TEXT, startIndex=6, endIndex=11, text='blind'] -- Token[type=ALTERNATION, startIndex=11, endIndex=12, text='/'] -- Token[type=TEXT, startIndex=12, endIndex=19, text='cripple'] -- Token[type=WHITE_SPACE, startIndex=19, endIndex=20, text=' '] -- Token[type=TEXT, startIndex=20, endIndex=24, text='mice'] -- Token[type=END_OF_LINE, startIndex=24, endIndex=24, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=5, text='three'] +- Token[type=WHITE_SPACE, start=5, end=6, text=' '] +- Token[type=TEXT, start=6, end=11, text='blind'] +- Token[type=ALTERNATION, start=11, end=12, text='/'] +- Token[type=TEXT, start=12, end=19, text='cripple'] +- Token[type=WHITE_SPACE, start=19, end=20, text=' '] +- Token[type=TEXT, start=20, end=24, text='mice'] +- Token[type=END_OF_LINE, start=24, end=24, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/alternation.yaml b/cucumber-expressions/java/testdata/tokens/alternation.yaml index 1f9c5b4c61..8a2c1deec0 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation.yaml @@ -1,9 +1,9 @@ exception: null expression: blind/cripple elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=5, text='blind'] -- Token[type=ALTERNATION, startIndex=5, endIndex=6, text='/'] -- Token[type=TEXT, startIndex=6, endIndex=13, text='cripple'] -- Token[type=END_OF_LINE, startIndex=13, endIndex=13, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=5, text='blind'] +- Token[type=ALTERNATION, start=5, end=6, text='/'] +- Token[type=TEXT, start=6, end=13, text='cripple'] +- Token[type=END_OF_LINE, start=13, end=13, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/empty-string.yaml b/cucumber-expressions/java/testdata/tokens/empty-string.yaml index 86fedfa01e..7ac995ec8a 100644 --- a/cucumber-expressions/java/testdata/tokens/empty-string.yaml +++ b/cucumber-expressions/java/testdata/tokens/empty-string.yaml @@ -1,6 +1,6 @@ exception: null expression: '' elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=END_OF_LINE, startIndex=0, endIndex=0, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=END_OF_LINE, start=0, end=0, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml index 4017884cc2..d7f852f740 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml @@ -1,9 +1,9 @@ exception: null expression: ' \/ ' elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=WHITE_SPACE, startIndex=0, endIndex=1, text=' '] -- Token[type=TEXT, startIndex=1, endIndex=3, text='/'] -- Token[type=WHITE_SPACE, startIndex=3, endIndex=4, text=' '] -- Token[type=END_OF_LINE, startIndex=4, endIndex=4, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=WHITE_SPACE, start=0, end=1, text=' '] +- Token[type=TEXT, start=1, end=3, text='/'] +- Token[type=WHITE_SPACE, start=3, end=4, text=' '] +- Token[type=END_OF_LINE, start=4, end=4, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml index 35d49bd4e9..f4fedd3398 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml @@ -1,9 +1,9 @@ exception: null expression: blind\ and\ famished\/cripple mice elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=29, text='blind and famished/cripple'] -- Token[type=WHITE_SPACE, startIndex=29, endIndex=30, text=' '] -- Token[type=TEXT, startIndex=30, endIndex=34, text='mice'] -- Token[type=END_OF_LINE, startIndex=34, endIndex=34, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=29, text='blind and famished/cripple'] +- Token[type=WHITE_SPACE, start=29, end=30, text=' '] +- Token[type=TEXT, start=30, end=34, text='mice'] +- Token[type=END_OF_LINE, start=34, end=34, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml index cc916a885b..6c6fa029f9 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml @@ -1,7 +1,7 @@ exception: null expression: \(blind\) elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=9, text='(blind)'] -- Token[type=END_OF_LINE, startIndex=9, endIndex=9, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=9, text='(blind)'] +- Token[type=END_OF_LINE, start=9, end=9, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml index bba974abec..83f9087fb2 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml @@ -1,7 +1,7 @@ exception: null expression: \{string\} elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=10, text='{string}'] -- Token[type=END_OF_LINE, startIndex=10, endIndex=10, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=10, text='{string}'] +- Token[type=END_OF_LINE, start=10, end=10, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml index 8c13fbe951..3b38318d18 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml @@ -1,7 +1,7 @@ exception: null expression: '\ ' elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=2, text=' '] -- Token[type=END_OF_LINE, startIndex=2, endIndex=2, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=2, text=' '] +- Token[type=END_OF_LINE, start=2, end=2, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml index b3ed6e3ac8..8ef25f96d9 100644 --- a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml @@ -1,13 +1,13 @@ exception: null expression: three (blind) mice elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=5, text='three'] -- Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] -- Token[type=BEGIN_OPTIONAL, startIndex=6, endIndex=7, text='('] -- Token[type=TEXT, startIndex=7, endIndex=12, text='blind'] -- Token[type=END_OPTIONAL, startIndex=12, endIndex=13, text=')'] -- Token[type=WHITE_SPACE, startIndex=13, endIndex=14, text=' '] -- Token[type=TEXT, startIndex=14, endIndex=18, text='mice'] -- Token[type=END_OF_LINE, startIndex=18, endIndex=18, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=5, text='three'] +- Token[type=WHITE_SPACE, start=5, end=6, text=' '] +- Token[type=BEGIN_OPTIONAL, start=6, end=7, text='('] +- Token[type=TEXT, start=7, end=12, text='blind'] +- Token[type=END_OPTIONAL, start=12, end=13, text=')'] +- Token[type=WHITE_SPACE, start=13, end=14, text=' '] +- Token[type=TEXT, start=14, end=18, text='mice'] +- Token[type=END_OF_LINE, start=18, end=18, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/optional.yaml b/cucumber-expressions/java/testdata/tokens/optional.yaml index 949eaf9028..8c4f341dca 100644 --- a/cucumber-expressions/java/testdata/tokens/optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional.yaml @@ -1,9 +1,9 @@ exception: null expression: (blind) elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=BEGIN_OPTIONAL, startIndex=0, endIndex=1, text='('] -- Token[type=TEXT, startIndex=1, endIndex=6, text='blind'] -- Token[type=END_OPTIONAL, startIndex=6, endIndex=7, text=')'] -- Token[type=END_OF_LINE, startIndex=7, endIndex=7, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=BEGIN_OPTIONAL, start=0, end=1, text='('] +- Token[type=TEXT, start=1, end=6, text='blind'] +- Token[type=END_OPTIONAL, start=6, end=7, text=')'] +- Token[type=END_OF_LINE, start=7, end=7, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml index aa931293c0..c97b7b2ddd 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml @@ -1,13 +1,13 @@ exception: null expression: three {string} mice elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=5, text='three'] -- Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] -- Token[type=BEGIN_PARAMETER, startIndex=6, endIndex=7, text='{'] -- Token[type=TEXT, startIndex=7, endIndex=13, text='string'] -- Token[type=END_PARAMETER, startIndex=13, endIndex=14, text='}'] -- Token[type=WHITE_SPACE, startIndex=14, endIndex=15, text=' '] -- Token[type=TEXT, startIndex=15, endIndex=19, text='mice'] -- Token[type=END_OF_LINE, startIndex=19, endIndex=19, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=5, text='three'] +- Token[type=WHITE_SPACE, start=5, end=6, text=' '] +- Token[type=BEGIN_PARAMETER, start=6, end=7, text='{'] +- Token[type=TEXT, start=7, end=13, text='string'] +- Token[type=END_PARAMETER, start=13, end=14, text='}'] +- Token[type=WHITE_SPACE, start=14, end=15, text=' '] +- Token[type=TEXT, start=15, end=19, text='mice'] +- Token[type=END_OF_LINE, start=19, end=19, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/parameter.yaml b/cucumber-expressions/java/testdata/tokens/parameter.yaml index cca7b599b3..e2c1005248 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter.yaml @@ -1,9 +1,9 @@ exception: null expression: '{string}' elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=BEGIN_PARAMETER, startIndex=0, endIndex=1, text='{'] -- Token[type=TEXT, startIndex=1, endIndex=7, text='string'] -- Token[type=END_PARAMETER, startIndex=7, endIndex=8, text='}'] -- Token[type=END_OF_LINE, startIndex=8, endIndex=8, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=BEGIN_PARAMETER, start=0, end=1, text='{'] +- Token[type=TEXT, start=1, end=7, text='string'] +- Token[type=END_PARAMETER, start=7, end=8, text='}'] +- Token[type=END_OF_LINE, start=8, end=8, text=''] diff --git a/cucumber-expressions/java/testdata/tokens/phrase.yaml b/cucumber-expressions/java/testdata/tokens/phrase.yaml index 5f1168370a..4e1d8edc15 100644 --- a/cucumber-expressions/java/testdata/tokens/phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/phrase.yaml @@ -1,11 +1,11 @@ exception: null expression: three blind mice elements: -- Token[type=START_OF_LINE, startIndex=0, endIndex=0, text=''] -- Token[type=TEXT, startIndex=0, endIndex=5, text='three'] -- Token[type=WHITE_SPACE, startIndex=5, endIndex=6, text=' '] -- Token[type=TEXT, startIndex=6, endIndex=11, text='blind'] -- Token[type=WHITE_SPACE, startIndex=11, endIndex=12, text=' '] -- Token[type=TEXT, startIndex=12, endIndex=16, text='mice'] -- Token[type=END_OF_LINE, startIndex=16, endIndex=16, text=''] +- Token[type=START_OF_LINE, start=0, end=0, text=''] +- Token[type=TEXT, start=0, end=5, text='three'] +- Token[type=WHITE_SPACE, start=5, end=6, text=' '] +- Token[type=TEXT, start=6, end=11, text='blind'] +- Token[type=WHITE_SPACE, start=11, end=12, text=' '] +- Token[type=TEXT, start=12, end=16, text='mice'] +- Token[type=END_OF_LINE, start=16, end=16, text=''] From 139d9b0334d43f19d5486560f401914ed3973899 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 21:33:05 +0200 Subject: [PATCH 098/183] Render element as json with single quotes (because yaml) --- .../io/cucumber/cucumberexpressions/Ast.java | 15 +++++++------- .../java/testdata/ast/alternation.yaml | 15 ++++++++++++++ .../ast/alternationFollowedByOptional.yaml | 20 +++++++++++++++++++ .../java/testdata/ast/alternationPhrase.yaml | 19 ++++++++++++++++++ .../ast/alternationWithUnusedEndOptional.yaml | 18 +++++++++++++++++ .../alternationWithUnusedStartOptional.yaml | 11 ++++++++++ .../ast/alternationWithWhiteSpace.yaml | 15 ++++++++++++++ .../java/testdata/ast/anonymousParameter.yaml | 8 ++++++++ .../java/testdata/ast/closingBrace.yaml | 8 ++++++++ .../java/testdata/ast/closingParenthesis.yaml | 8 ++++++++ .../java/testdata/ast/emptyAlternation.yaml | 11 ++++++++++ .../java/testdata/ast/emptyAlternations.yaml | 12 +++++++++++ .../java/testdata/ast/emptyString.yaml | 5 +++++ .../java/testdata/ast/escapedAlternation.yaml | 8 ++++++++ .../java/testdata/ast/escapedBackSlash.yaml | 8 ++++++++ .../ast/escapedOpeningParenthesis.yaml | 8 ++++++++ .../java/testdata/ast/escapedOptional.yaml | 9 +++++++++ .../escapedOptionalFollowedByOptional.yaml | 18 +++++++++++++++++ .../testdata/ast/escapedOptionalPhrase.yaml | 13 ++++++++++++ .../java/testdata/ast/openingBrace.yaml | 11 ++++++++++ .../java/testdata/ast/openingParenthesis.yaml | 11 ++++++++++ .../java/testdata/ast/optional.yaml | 10 ++++++++++ .../optionalContainingEscapedOptional.yaml | 17 ++++++++++++++++ .../java/testdata/ast/optionalPhrase.yaml | 14 +++++++++++++ .../java/testdata/ast/parameter.yaml | 10 ++++++++++ .../java/testdata/ast/phrase.yaml | 12 +++++++++++ .../testdata/ast/unfinishedParameter.yaml | 11 ++++++++++ 27 files changed, 318 insertions(+), 7 deletions(-) create mode 100644 cucumber-expressions/java/testdata/ast/alternation.yaml create mode 100644 cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml create mode 100644 cucumber-expressions/java/testdata/ast/alternationPhrase.yaml create mode 100644 cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml create mode 100644 cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml create mode 100644 cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml create mode 100644 cucumber-expressions/java/testdata/ast/anonymousParameter.yaml create mode 100644 cucumber-expressions/java/testdata/ast/closingBrace.yaml create mode 100644 cucumber-expressions/java/testdata/ast/closingParenthesis.yaml create mode 100644 cucumber-expressions/java/testdata/ast/emptyAlternation.yaml create mode 100644 cucumber-expressions/java/testdata/ast/emptyAlternations.yaml create mode 100644 cucumber-expressions/java/testdata/ast/emptyString.yaml create mode 100644 cucumber-expressions/java/testdata/ast/escapedAlternation.yaml create mode 100644 cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml create mode 100644 cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml create mode 100644 cucumber-expressions/java/testdata/ast/escapedOptional.yaml create mode 100644 cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml create mode 100644 cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml create mode 100644 cucumber-expressions/java/testdata/ast/openingBrace.yaml create mode 100644 cucumber-expressions/java/testdata/ast/openingParenthesis.yaml create mode 100644 cucumber-expressions/java/testdata/ast/optional.yaml create mode 100644 cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml create mode 100644 cucumber-expressions/java/testdata/ast/optionalPhrase.yaml create mode 100644 cucumber-expressions/java/testdata/ast/parameter.yaml create mode 100644 cucumber-expressions/java/testdata/ast/phrase.yaml create mode 100644 cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index d1a72fc933..f7b20ee4ee 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -97,20 +97,20 @@ private StringBuilder toString(int depth) { for (int i = 0; i < depth; i++) { sb.append(" "); } - sb.append("AstNode[") - .append("type=").append(type) - .append("', start='") + sb.append("{") + .append("type: '").append(type) + .append("', start: '") .append(start) - .append("', end=") + .append("', end: ") .append(end) .append("'"); if (token != null) { - sb.append(", token=").append(token); + sb.append(", token: '").append(token).append("'"); } if (nodes != null && !nodes.isEmpty()) { - sb.append("\n"); + sb.append(", nodes: [\n"); for (Node node : nodes) { sb.append(node.toString(depth + 1)); sb.append("\n"); @@ -118,9 +118,10 @@ private StringBuilder toString(int depth) { for (int i = 0; i < depth; i++) { sb.append(" "); } + sb.append("]"); } - sb.append(']'); + sb.append('}'); return sb; } diff --git a/cucumber-expressions/java/testdata/ast/alternation.yaml b/cucumber-expressions/java/testdata/ast/alternation.yaml new file mode 100644 index 0000000000..9ab4a9c7da --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/alternation.yaml @@ -0,0 +1,15 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 9', nodes: [ + {type: 'ALTERNATION_NODE', start: '0', end: 9', nodes: [ + {type: 'ALTERNATIVE_NODE', start: '0', end: 4', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 4', token: 'mice'} + ]} + {type: 'ALTERNATIVE_NODE', start: '5', end: 9', nodes: [ + {type: 'TEXT_NODE', start: '5', end: 9', token: 'rats'} + ]} + ]} + ]} +elements: null +exception: null +expression: mice/rats + diff --git a/cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml b/cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml new file mode 100644 index 0000000000..9ff543f3c8 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml @@ -0,0 +1,20 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 23', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} + {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} + {type: 'ALTERNATION_NODE', start: '6', end: 23', nodes: [ + {type: 'ALTERNATIVE_NODE', start: '6', end: 16', nodes: [ + {type: 'TEXT_NODE', start: '6', end: 16', token: 'blind rat'} + ]} + {type: 'ALTERNATIVE_NODE', start: '17', end: 23', nodes: [ + {type: 'TEXT_NODE', start: '17', end: 20', token: 'cat'} + {type: 'OPTIONAL_NODE', start: '20', end: 23', nodes: [ + {type: 'TEXT_NODE', start: '21', end: 22', token: 's'} + ]} + ]} + ]} + ]} +elements: null +exception: null +expression: three blind\ rat/cat(s) + diff --git a/cucumber-expressions/java/testdata/ast/alternationPhrase.yaml b/cucumber-expressions/java/testdata/ast/alternationPhrase.yaml new file mode 100644 index 0000000000..75fcf3e151 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/alternationPhrase.yaml @@ -0,0 +1,19 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 23', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} + {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} + {type: 'ALTERNATION_NODE', start: '6', end: 18', nodes: [ + {type: 'ALTERNATIVE_NODE', start: '6', end: 12', nodes: [ + {type: 'TEXT_NODE', start: '6', end: 12', token: 'hungry'} + ]} + {type: 'ALTERNATIVE_NODE', start: '13', end: 18', nodes: [ + {type: 'TEXT_NODE', start: '13', end: 18', token: 'blind'} + ]} + ]} + {type: 'TEXT_NODE', start: '18', end: 19', token: ' '} + {type: 'TEXT_NODE', start: '19', end: 23', token: 'mice'} + ]} +elements: null +exception: null +expression: three hungry/blind mice + diff --git a/cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml b/cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml new file mode 100644 index 0000000000..e50e1d2b15 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml @@ -0,0 +1,18 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 23', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} + {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} + {type: 'ALTERNATION_NODE', start: '6', end: 23', nodes: [ + {type: 'ALTERNATIVE_NODE', start: '6', end: 18', nodes: [ + {type: 'TEXT_NODE', start: '6', end: 7', token: ')'} + {type: 'TEXT_NODE', start: '7', end: 18', token: 'blind mice'} + ]} + {type: 'ALTERNATIVE_NODE', start: '19', end: 23', nodes: [ + {type: 'TEXT_NODE', start: '19', end: 23', token: 'rats'} + ]} + ]} + ]} +elements: null +exception: null +expression: three )blind\ mice/rats + diff --git a/cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml b/cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml new file mode 100644 index 0000000000..3a6ede9811 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml @@ -0,0 +1,11 @@ +element: null +elements: null +exception: |- + This Cucumber Expression has a problem at column 23: + + three blind\ mice/rats( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text +expression: three blind\ mice/rats( + diff --git a/cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml b/cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml new file mode 100644 index 0000000000..db1c920584 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml @@ -0,0 +1,15 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 29', nodes: [ + {type: 'ALTERNATION_NODE', start: '0', end: 29', nodes: [ + {type: 'ALTERNATIVE_NODE', start: '0', end: 15', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 15', token: ' three hungry'} + ]} + {type: 'ALTERNATIVE_NODE', start: '16', end: 29', nodes: [ + {type: 'TEXT_NODE', start: '16', end: 29', token: 'blind mice '} + ]} + ]} + ]} +elements: null +exception: null +expression: '\ three\ hungry/blind\ mice\ ' + diff --git a/cucumber-expressions/java/testdata/ast/anonymousParameter.yaml b/cucumber-expressions/java/testdata/ast/anonymousParameter.yaml new file mode 100644 index 0000000000..02aab824e2 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/anonymousParameter.yaml @@ -0,0 +1,8 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ + {type: 'PARAMETER_NODE', start: '0', end: 2'} + ]} +elements: null +exception: null +expression: '{}' + diff --git a/cucumber-expressions/java/testdata/ast/closingBrace.yaml b/cucumber-expressions/java/testdata/ast/closingBrace.yaml new file mode 100644 index 0000000000..26ce493661 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/closingBrace.yaml @@ -0,0 +1,8 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 1', token: '}'} + ]} +elements: null +exception: null +expression: '}' + diff --git a/cucumber-expressions/java/testdata/ast/closingParenthesis.yaml b/cucumber-expressions/java/testdata/ast/closingParenthesis.yaml new file mode 100644 index 0000000000..094fdd55cc --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/closingParenthesis.yaml @@ -0,0 +1,8 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 1', token: ')'} + ]} +elements: null +exception: null +expression: ) + diff --git a/cucumber-expressions/java/testdata/ast/emptyAlternation.yaml b/cucumber-expressions/java/testdata/ast/emptyAlternation.yaml new file mode 100644 index 0000000000..892d214ecb --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/emptyAlternation.yaml @@ -0,0 +1,11 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ + {type: 'ALTERNATION_NODE', start: '0', end: 1', nodes: [ + {type: 'ALTERNATIVE_NODE', start: '0', end: 0'} + {type: 'ALTERNATIVE_NODE', start: '1', end: 1'} + ]} + ]} +elements: null +exception: null +expression: / + diff --git a/cucumber-expressions/java/testdata/ast/emptyAlternations.yaml b/cucumber-expressions/java/testdata/ast/emptyAlternations.yaml new file mode 100644 index 0000000000..5acf31a948 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/emptyAlternations.yaml @@ -0,0 +1,12 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ + {type: 'ALTERNATION_NODE', start: '0', end: 2', nodes: [ + {type: 'ALTERNATIVE_NODE', start: '0', end: 0'} + {type: 'ALTERNATIVE_NODE', start: '1', end: 1'} + {type: 'ALTERNATIVE_NODE', start: '2', end: 2'} + ]} + ]} +elements: null +exception: null +expression: // + diff --git a/cucumber-expressions/java/testdata/ast/emptyString.yaml b/cucumber-expressions/java/testdata/ast/emptyString.yaml new file mode 100644 index 0000000000..ed6b1ec078 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/emptyString.yaml @@ -0,0 +1,5 @@ +element: '{type: ''EXPRESSION_NODE'', start: ''0'', end: 0''}' +elements: null +exception: null +expression: '' + diff --git a/cucumber-expressions/java/testdata/ast/escapedAlternation.yaml b/cucumber-expressions/java/testdata/ast/escapedAlternation.yaml new file mode 100644 index 0000000000..3c445d3c0a --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/escapedAlternation.yaml @@ -0,0 +1,8 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 10', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 10', token: 'mice/rats'} + ]} +elements: null +exception: null +expression: mice\/rats + diff --git a/cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml b/cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml new file mode 100644 index 0000000000..b12a713b2b --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml @@ -0,0 +1,8 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 2', token: '\'} + ]} +elements: null +exception: null +expression: \\ + diff --git a/cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml b/cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml new file mode 100644 index 0000000000..ad23873573 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml @@ -0,0 +1,8 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 2', token: '('} + ]} +elements: null +exception: null +expression: \( + diff --git a/cucumber-expressions/java/testdata/ast/escapedOptional.yaml b/cucumber-expressions/java/testdata/ast/escapedOptional.yaml new file mode 100644 index 0000000000..7bd72d848f --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/escapedOptional.yaml @@ -0,0 +1,9 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 8', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 7', token: '(blind'} + {type: 'TEXT_NODE', start: '7', end: 8', token: ')'} + ]} +elements: null +exception: null +expression: \(blind) + diff --git a/cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml b/cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml new file mode 100644 index 0000000000..46c966211a --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml @@ -0,0 +1,18 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 26', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} + {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} + {type: 'TEXT_NODE', start: '6', end: 8', token: '('} + {type: 'OPTIONAL_NODE', start: '8', end: 14', nodes: [ + {type: 'TEXT_NODE', start: '9', end: 13', token: 'very'} + ]} + {type: 'TEXT_NODE', start: '14', end: 15', token: ' '} + {type: 'TEXT_NODE', start: '15', end: 20', token: 'blind'} + {type: 'TEXT_NODE', start: '20', end: 21', token: ')'} + {type: 'TEXT_NODE', start: '21', end: 22', token: ' '} + {type: 'TEXT_NODE', start: '22', end: 26', token: 'mice'} + ]} +elements: null +exception: null +expression: three \((very) blind) mice + diff --git a/cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml b/cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml new file mode 100644 index 0000000000..da2da9dc81 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml @@ -0,0 +1,13 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 19', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} + {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} + {type: 'TEXT_NODE', start: '6', end: 13', token: '(blind'} + {type: 'TEXT_NODE', start: '13', end: 14', token: ')'} + {type: 'TEXT_NODE', start: '14', end: 15', token: ' '} + {type: 'TEXT_NODE', start: '15', end: 19', token: 'mice'} + ]} +elements: null +exception: null +expression: three \(blind) mice + diff --git a/cucumber-expressions/java/testdata/ast/openingBrace.yaml b/cucumber-expressions/java/testdata/ast/openingBrace.yaml new file mode 100644 index 0000000000..b58491de63 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/openingBrace.yaml @@ -0,0 +1,11 @@ +element: null +elements: null +exception: |- + This Cucumber Expression has a problem at column 1: + + { + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter +expression: '{' + diff --git a/cucumber-expressions/java/testdata/ast/openingParenthesis.yaml b/cucumber-expressions/java/testdata/ast/openingParenthesis.yaml new file mode 100644 index 0000000000..52cf41e47e --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/openingParenthesis.yaml @@ -0,0 +1,11 @@ +element: null +elements: null +exception: |- + This Cucumber Expression has a problem at column 1: + + ( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text +expression: ( + diff --git a/cucumber-expressions/java/testdata/ast/optional.yaml b/cucumber-expressions/java/testdata/ast/optional.yaml new file mode 100644 index 0000000000..0b81f4cd45 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/optional.yaml @@ -0,0 +1,10 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 7', nodes: [ + {type: 'OPTIONAL_NODE', start: '0', end: 7', nodes: [ + {type: 'TEXT_NODE', start: '1', end: 6', token: 'blind'} + ]} + ]} +elements: null +exception: null +expression: (blind) + diff --git a/cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml b/cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml new file mode 100644 index 0000000000..d3cc971904 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml @@ -0,0 +1,17 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 26', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} + {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} + {type: 'OPTIONAL_NODE', start: '6', end: 21', nodes: [ + {type: 'TEXT_NODE', start: '7', end: 8', token: '('} + {type: 'TEXT_NODE', start: '8', end: 14', token: 'very)'} + {type: 'TEXT_NODE', start: '14', end: 15', token: ' '} + {type: 'TEXT_NODE', start: '15', end: 20', token: 'blind'} + ]} + {type: 'TEXT_NODE', start: '21', end: 22', token: ' '} + {type: 'TEXT_NODE', start: '22', end: 26', token: 'mice'} + ]} +elements: null +exception: null +expression: three ((very\) blind) mice + diff --git a/cucumber-expressions/java/testdata/ast/optionalPhrase.yaml b/cucumber-expressions/java/testdata/ast/optionalPhrase.yaml new file mode 100644 index 0000000000..4d26badf92 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/optionalPhrase.yaml @@ -0,0 +1,14 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 18', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} + {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} + {type: 'OPTIONAL_NODE', start: '6', end: 13', nodes: [ + {type: 'TEXT_NODE', start: '7', end: 12', token: 'blind'} + ]} + {type: 'TEXT_NODE', start: '13', end: 14', token: ' '} + {type: 'TEXT_NODE', start: '14', end: 18', token: 'mice'} + ]} +elements: null +exception: null +expression: three (blind) mice + diff --git a/cucumber-expressions/java/testdata/ast/parameter.yaml b/cucumber-expressions/java/testdata/ast/parameter.yaml new file mode 100644 index 0000000000..84bd6358d8 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/parameter.yaml @@ -0,0 +1,10 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 8', nodes: [ + {type: 'PARAMETER_NODE', start: '0', end: 8', nodes: [ + {type: 'TEXT_NODE', start: '1', end: 7', token: 'string'} + ]} + ]} +elements: null +exception: null +expression: '{string}' + diff --git a/cucumber-expressions/java/testdata/ast/phrase.yaml b/cucumber-expressions/java/testdata/ast/phrase.yaml new file mode 100644 index 0000000000..73fea9f321 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/phrase.yaml @@ -0,0 +1,12 @@ +element: |- + {type: 'EXPRESSION_NODE', start: '0', end: 16', nodes: [ + {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} + {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} + {type: 'TEXT_NODE', start: '6', end: 11', token: 'blind'} + {type: 'TEXT_NODE', start: '11', end: 12', token: ' '} + {type: 'TEXT_NODE', start: '12', end: 16', token: 'mice'} + ]} +elements: null +exception: null +expression: three blind mice + diff --git a/cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml b/cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml new file mode 100644 index 0000000000..b1c9ac67f9 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml @@ -0,0 +1,11 @@ +element: null +elements: null +exception: |- + This Cucumber Expression has a problem at column 1: + + {string + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter +expression: '{string' + From a8dc5705c6152088be3354508ff00ee49a1d73cd Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 21:40:06 +0200 Subject: [PATCH 099/183] Render ast elements as json with single quotes (because yaml) --- .../io/cucumber/cucumberexpressions/Ast.java | 10 +++++----- .../testdata/tokens/alternation-phrase.yaml | 18 +++++++++--------- .../java/testdata/tokens/alternation.yaml | 10 +++++----- .../java/testdata/tokens/empty-string.yaml | 4 ++-- ...ape-char-has-start-index-of-text-token.yaml | 10 +++++----- .../testdata/tokens/escaped-alternation.yaml | 10 +++++----- .../java/testdata/tokens/escaped-optional.yaml | 6 +++--- .../testdata/tokens/escaped-parameter.yaml | 6 +++--- .../java/testdata/tokens/escaped-space.yaml | 6 +++--- .../java/testdata/tokens/optional-phrase.yaml | 18 +++++++++--------- .../java/testdata/tokens/optional.yaml | 10 +++++----- .../java/testdata/tokens/parameter-phrase.yaml | 18 +++++++++--------- .../java/testdata/tokens/parameter.yaml | 10 +++++----- .../java/testdata/tokens/phrase.yaml | 14 +++++++------- 14 files changed, 75 insertions(+), 75 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index f7b20ee4ee..68437b561a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -227,11 +227,11 @@ public int hashCode() { @Override public String toString() { - return new StringJoiner(", ", Token.class.getSimpleName() + "[", "]") - .add("type=" + type) - .add("start=" + start) - .add("end=" + end) - .add("text='" + text + "'") + return new StringJoiner(", ", "" + "{", "}") + .add("type: '" + type + "'") + .add("start: '" + start + "'") + .add("end: '" + end + "'") + .add("text: '" + text + "'") .toString(); } diff --git a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml index c2bb640771..c4086ef2dd 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml @@ -1,13 +1,13 @@ exception: null expression: three blind/cripple mice elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=5, text='three'] -- Token[type=WHITE_SPACE, start=5, end=6, text=' '] -- Token[type=TEXT, start=6, end=11, text='blind'] -- Token[type=ALTERNATION, start=11, end=12, text='/'] -- Token[type=TEXT, start=12, end=19, text='cripple'] -- Token[type=WHITE_SPACE, start=19, end=20, text=' '] -- Token[type=TEXT, start=20, end=24, text='mice'] -- Token[type=END_OF_LINE, start=24, end=24, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '5', text: 'three'}" +- "{type: 'WHITE_SPACE', start: '5', end: '6', text: ' '}" +- "{type: 'TEXT', start: '6', end: '11', text: 'blind'}" +- "{type: 'ALTERNATION', start: '11', end: '12', text: '/'}" +- "{type: 'TEXT', start: '12', end: '19', text: 'cripple'}" +- "{type: 'WHITE_SPACE', start: '19', end: '20', text: ' '}" +- "{type: 'TEXT', start: '20', end: '24', text: 'mice'}" +- "{type: 'END_OF_LINE', start: '24', end: '24', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/alternation.yaml b/cucumber-expressions/java/testdata/tokens/alternation.yaml index 8a2c1deec0..f102d0fee2 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation.yaml @@ -1,9 +1,9 @@ exception: null expression: blind/cripple elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=5, text='blind'] -- Token[type=ALTERNATION, start=5, end=6, text='/'] -- Token[type=TEXT, start=6, end=13, text='cripple'] -- Token[type=END_OF_LINE, start=13, end=13, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '5', text: 'blind'}" +- "{type: 'ALTERNATION', start: '5', end: '6', text: '/'}" +- "{type: 'TEXT', start: '6', end: '13', text: 'cripple'}" +- "{type: 'END_OF_LINE', start: '13', end: '13', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/empty-string.yaml b/cucumber-expressions/java/testdata/tokens/empty-string.yaml index 7ac995ec8a..7801ca7c42 100644 --- a/cucumber-expressions/java/testdata/tokens/empty-string.yaml +++ b/cucumber-expressions/java/testdata/tokens/empty-string.yaml @@ -1,6 +1,6 @@ exception: null expression: '' elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=END_OF_LINE, start=0, end=0, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'END_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml index d7f852f740..ad9649931d 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml @@ -1,9 +1,9 @@ exception: null expression: ' \/ ' elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=WHITE_SPACE, start=0, end=1, text=' '] -- Token[type=TEXT, start=1, end=3, text='/'] -- Token[type=WHITE_SPACE, start=3, end=4, text=' '] -- Token[type=END_OF_LINE, start=4, end=4, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'WHITE_SPACE', start: '0', end: '1', text: ' '}" +- "{type: 'TEXT', start: '1', end: '3', text: '/'}" +- "{type: 'WHITE_SPACE', start: '3', end: '4', text: ' '}" +- "{type: 'END_OF_LINE', start: '4', end: '4', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml index f4fedd3398..4d2e67b372 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml @@ -1,9 +1,9 @@ exception: null expression: blind\ and\ famished\/cripple mice elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=29, text='blind and famished/cripple'] -- Token[type=WHITE_SPACE, start=29, end=30, text=' '] -- Token[type=TEXT, start=30, end=34, text='mice'] -- Token[type=END_OF_LINE, start=34, end=34, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '29', text: 'blind and famished/cripple'}" +- "{type: 'WHITE_SPACE', start: '29', end: '30', text: ' '}" +- "{type: 'TEXT', start: '30', end: '34', text: 'mice'}" +- "{type: 'END_OF_LINE', start: '34', end: '34', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml index 6c6fa029f9..c16bc9b1be 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml @@ -1,7 +1,7 @@ exception: null expression: \(blind\) elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=9, text='(blind)'] -- Token[type=END_OF_LINE, start=9, end=9, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '9', text: '(blind)'}" +- "{type: 'END_OF_LINE', start: '9', end: '9', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml index 83f9087fb2..9f5bd674dc 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml @@ -1,7 +1,7 @@ exception: null expression: \{string\} elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=10, text='{string}'] -- Token[type=END_OF_LINE, start=10, end=10, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '10', text: '{string}'}" +- "{type: 'END_OF_LINE', start: '10', end: '10', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml index 3b38318d18..532cf8761f 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml @@ -1,7 +1,7 @@ exception: null expression: '\ ' elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=2, text=' '] -- Token[type=END_OF_LINE, start=2, end=2, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '2', text: ' '}" +- "{type: 'END_OF_LINE', start: '2', end: '2', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml index 8ef25f96d9..f14d693e89 100644 --- a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml @@ -1,13 +1,13 @@ exception: null expression: three (blind) mice elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=5, text='three'] -- Token[type=WHITE_SPACE, start=5, end=6, text=' '] -- Token[type=BEGIN_OPTIONAL, start=6, end=7, text='('] -- Token[type=TEXT, start=7, end=12, text='blind'] -- Token[type=END_OPTIONAL, start=12, end=13, text=')'] -- Token[type=WHITE_SPACE, start=13, end=14, text=' '] -- Token[type=TEXT, start=14, end=18, text='mice'] -- Token[type=END_OF_LINE, start=18, end=18, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '5', text: 'three'}" +- "{type: 'WHITE_SPACE', start: '5', end: '6', text: ' '}" +- "{type: 'BEGIN_OPTIONAL', start: '6', end: '7', text: '('}" +- "{type: 'TEXT', start: '7', end: '12', text: 'blind'}" +- "{type: 'END_OPTIONAL', start: '12', end: '13', text: ')'}" +- "{type: 'WHITE_SPACE', start: '13', end: '14', text: ' '}" +- "{type: 'TEXT', start: '14', end: '18', text: 'mice'}" +- "{type: 'END_OF_LINE', start: '18', end: '18', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/optional.yaml b/cucumber-expressions/java/testdata/tokens/optional.yaml index 8c4f341dca..189fe77eae 100644 --- a/cucumber-expressions/java/testdata/tokens/optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional.yaml @@ -1,9 +1,9 @@ exception: null expression: (blind) elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=BEGIN_OPTIONAL, start=0, end=1, text='('] -- Token[type=TEXT, start=1, end=6, text='blind'] -- Token[type=END_OPTIONAL, start=6, end=7, text=')'] -- Token[type=END_OF_LINE, start=7, end=7, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'BEGIN_OPTIONAL', start: '0', end: '1', text: '('}" +- "{type: 'TEXT', start: '1', end: '6', text: 'blind'}" +- "{type: 'END_OPTIONAL', start: '6', end: '7', text: ')'}" +- "{type: 'END_OF_LINE', start: '7', end: '7', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml index c97b7b2ddd..3a887fb0d1 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml @@ -1,13 +1,13 @@ exception: null expression: three {string} mice elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=5, text='three'] -- Token[type=WHITE_SPACE, start=5, end=6, text=' '] -- Token[type=BEGIN_PARAMETER, start=6, end=7, text='{'] -- Token[type=TEXT, start=7, end=13, text='string'] -- Token[type=END_PARAMETER, start=13, end=14, text='}'] -- Token[type=WHITE_SPACE, start=14, end=15, text=' '] -- Token[type=TEXT, start=15, end=19, text='mice'] -- Token[type=END_OF_LINE, start=19, end=19, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '5', text: 'three'}" +- "{type: 'WHITE_SPACE', start: '5', end: '6', text: ' '}" +- "{type: 'BEGIN_PARAMETER', start: '6', end: '7', text: '{'}" +- "{type: 'TEXT', start: '7', end: '13', text: 'string'}" +- "{type: 'END_PARAMETER', start: '13', end: '14', text: '}'}" +- "{type: 'WHITE_SPACE', start: '14', end: '15', text: ' '}" +- "{type: 'TEXT', start: '15', end: '19', text: 'mice'}" +- "{type: 'END_OF_LINE', start: '19', end: '19', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/parameter.yaml b/cucumber-expressions/java/testdata/tokens/parameter.yaml index e2c1005248..56722d488d 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter.yaml @@ -1,9 +1,9 @@ exception: null expression: '{string}' elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=BEGIN_PARAMETER, start=0, end=1, text='{'] -- Token[type=TEXT, start=1, end=7, text='string'] -- Token[type=END_PARAMETER, start=7, end=8, text='}'] -- Token[type=END_OF_LINE, start=8, end=8, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'BEGIN_PARAMETER', start: '0', end: '1', text: '{'}" +- "{type: 'TEXT', start: '1', end: '7', text: 'string'}" +- "{type: 'END_PARAMETER', start: '7', end: '8', text: '}'}" +- "{type: 'END_OF_LINE', start: '8', end: '8', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/phrase.yaml b/cucumber-expressions/java/testdata/tokens/phrase.yaml index 4e1d8edc15..9a1c504884 100644 --- a/cucumber-expressions/java/testdata/tokens/phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/phrase.yaml @@ -1,11 +1,11 @@ exception: null expression: three blind mice elements: -- Token[type=START_OF_LINE, start=0, end=0, text=''] -- Token[type=TEXT, start=0, end=5, text='three'] -- Token[type=WHITE_SPACE, start=5, end=6, text=' '] -- Token[type=TEXT, start=6, end=11, text='blind'] -- Token[type=WHITE_SPACE, start=11, end=12, text=' '] -- Token[type=TEXT, start=12, end=16, text='mice'] -- Token[type=END_OF_LINE, start=16, end=16, text=''] +- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" +- "{type: 'TEXT', start: '0', end: '5', text: 'three'}" +- "{type: 'WHITE_SPACE', start: '5', end: '6', text: ' '}" +- "{type: 'TEXT', start: '6', end: '11', text: 'blind'}" +- "{type: 'WHITE_SPACE', start: '11', end: '12', text: ' '}" +- "{type: 'TEXT', start: '12', end: '16', text: 'mice'}" +- "{type: 'END_OF_LINE', start: '16', end: '16', text: ''}" From 62c4f760bdfb6cbf34b68525a93e388cbd87ee67 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 21:41:47 +0200 Subject: [PATCH 100/183] Remove tests --- .../CucumberExpressionParserTest.java | 376 ------------------ 1 file changed, 376 deletions(-) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 2618189581..f9c2a47915 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -38,382 +38,6 @@ class CucumberExpressionParserTest { private final CucumberExpressionParser parser = new CucumberExpressionParser(); - @Test - void emptyString() { - assertThat(astOf(""), equalTo( - new Node(EXPRESSION_NODE, 0, 0) - )); - } - - @Test - void phrase() { - assertThat(astOf("three blind mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 16, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(TEXT_NODE, 6, 11, "blind"), - new Node(TEXT_NODE, 11, 12, " "), - new Node(TEXT_NODE, 12, 16, "mice") - ) - )); - } - - @Test - void optional() { - assertThat(astOf("(blind)"), equalTo( - new Node(EXPRESSION_NODE, 0, 7, - new Node(OPTIONAL_NODE, 0, 7, - new Node(TEXT_NODE, 1, 6, "blind") - ) - ) - )); - } - - @Test - void parameter() { - assertThat(astOf("{string}"), equalTo( - new Node(EXPRESSION_NODE, 0, 8, - new Node(PARAMETER_NODE, 0, 8, - new Node(TEXT_NODE, 1, 7, "string") - ) - ) - )); - } - - @Test - void anonymousParameter() { - assertThat(astOf("{}"), equalTo( - new Node(EXPRESSION_NODE, 0, 2, - new Node(PARAMETER_NODE, 0, 2) - ) - )); - } - - @Test - void optionalPhrase() { - assertThat(astOf("three (blind) mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 18, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(OPTIONAL_NODE, 6, 13, - new Node(TEXT_NODE, 7, 12, "blind") - ), - new Node(TEXT_NODE, 13, 14, " "), - new Node(TEXT_NODE, 14, 18, "mice") - ) - )); - } - - @Test - void escapedBackSlash() { - assertThat(astOf("\\\\"), equalTo( - new Node(EXPRESSION_NODE, 0, 2, - new Node(TEXT_NODE, 0, 2, "\\") - ) - )); - } - - @Test - void openingBrace() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + - "\n" + - "{\n" + - "^\n" + - "The '{' does not have a matching '}'.\n" + - "If you did not intend to use a parameter you can use '\\{' to escape the a parameter" - )); - } - - @Test - void closingBrace() { - assertThat(astOf("}"), equalTo( - new Node(EXPRESSION_NODE, 0, 1, - new Node(TEXT_NODE, 0, 1, "}") - ) - )); - } - - @Test - void unfinishedParameter() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("{string")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + - "\n" + - "{string\n" + - "^\n" + - "The '{' does not have a matching '}'.\n" + - "If you did not intend to use a parameter you can use '\\{' to escape the a parameter")); - } - - @Test - void openingParenthesis() { - CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, () -> astOf("(")); - assertThat(exception.getMessage(), is("This Cucumber Expression has a problem at column 1:\n" + - "\n" + - "(\n" + - "^\n" + - "The '(' does not have a matching ')'.\n" + - "If you did not intend to use optional text you can use '\\(' to escape the optional text" - )); - } - - @Test - void closingParenthesis() { - assertThat(astOf(")"), equalTo( - new Node(EXPRESSION_NODE, 0, 1, - new Node(TEXT_NODE, 0, 1, ")") - ) - )); - } - - @Test - void escapedOpeningParenthesis() { - assertThat(astOf("\\("), equalTo( - new Node(EXPRESSION_NODE, 0, 2, - new Node(TEXT_NODE, 0, 2, "(") - ) - )); - } - - @Test - void escapedOptional() { - assertThat(astOf("\\(blind)"), equalTo( - new Node(EXPRESSION_NODE, 0, 8, - new Node(TEXT_NODE, 0, 7, "(blind"), - new Node(TEXT_NODE, 7, 8, ")") - ) - )); - } - - @Test - void escapedOptionalPhrase() { - assertThat(astOf("three \\(blind) mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 19, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(TEXT_NODE, 6, 13, "(blind"), - new Node(TEXT_NODE, 13, 14, ")"), - new Node(TEXT_NODE, 14, 15, " "), - new Node(TEXT_NODE, 15, 19, "mice") - ) - )); - } - - @Test - void escapedOptionalFollowedByOptional() { - assertThat(astOf("three \\((very) blind) mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 26, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(TEXT_NODE, 6, 8, "("), - new Node(OPTIONAL_NODE, 8, 14, - new Node(TEXT_NODE, 9, 13, "very") - ), - new Node(TEXT_NODE, 14, 15, " "), - new Node(TEXT_NODE, 15, 20, "blind"), - new Node(TEXT_NODE, 20, 21, ")"), - new Node(TEXT_NODE, 21, 22, " "), - new Node(TEXT_NODE, 22, 26, "mice") - ) - )); - } - - @Test - void optionalContainingEscapedOptional() { - assertThat(astOf("three ((very\\) blind) mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 26, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(OPTIONAL_NODE, 6, 21, - new Node(TEXT_NODE, 7, 8, "("), - new Node(TEXT_NODE, 8, 14, "very)"), - new Node(TEXT_NODE, 14, 15, " "), - new Node(TEXT_NODE, 15, 20, "blind") - ), - new Node(TEXT_NODE, 21, 22, " "), - new Node(TEXT_NODE, 22, 26, "mice") - ) - )); - } - - @Test - void alternation() { - assertThat(astOf("mice/rats"), equalTo( - new Node(EXPRESSION_NODE, 0, 9, - new Node(ALTERNATION_NODE, 0, 9, - new Node(ALTERNATIVE_NODE, 0, 4, - new Node(TEXT_NODE, 0, 4, "mice") - ), - new Node(ALTERNATIVE_NODE, 5, 9, - new Node(TEXT_NODE, 5, 9, "rats") - ) - ) - ) - )); - } - - @Test - void emptyAlternation() { - assertThat(astOf("/"), equalTo( - new Node(EXPRESSION_NODE, 0, 1, - new Node(ALTERNATION_NODE, 0, 1, - new Node(ALTERNATIVE_NODE, 0, 0), - new Node(ALTERNATIVE_NODE, 1, 1) - ) - ) - )); - } - - @Test - void emptyAlternations() { - assertThat(astOf("//"), equalTo( - new Node(EXPRESSION_NODE, 0, 2, - new Node(ALTERNATION_NODE, 0, 2, - new Node(ALTERNATIVE_NODE, 0, 0), - new Node(ALTERNATIVE_NODE, 1, 1), - new Node(ALTERNATIVE_NODE, 2, 2) - ) - ) - )); - } - - @Test - void escapedAlternation() { - assertThat(astOf("mice\\/rats"), equalTo( - new Node(EXPRESSION_NODE, 0, 10, - new Node(TEXT_NODE, 0, 10, "mice/rats") - ) - )); - } - - @Test - void alternationPhrase() { - assertThat(astOf("three hungry/blind mice"), equalTo( - new Node(EXPRESSION_NODE, 0, 23, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(ALTERNATION_NODE, 6, 18, - new Node(ALTERNATIVE_NODE, 6, 12, - new Node(TEXT_NODE, 6, 12, "hungry") - ), - new Node(ALTERNATIVE_NODE, 13, 18, - new Node(TEXT_NODE, 13, 18, "blind") - ) - ), - new Node(TEXT_NODE, 18, 19, " "), - new Node(TEXT_NODE, 19, 23, "mice") - ) - )); - } - - @Test - void alternationWithWhiteSpace() { - assertThat(astOf("\\ three\\ hungry/blind\\ mice\\ "), equalTo( - new Node(EXPRESSION_NODE, 0, 29, - new Node(ALTERNATION_NODE, 0, 29, - new Node(ALTERNATIVE_NODE, 0, 15, - new Node(TEXT_NODE, 0, 15, " three hungry") - ), - new Node(ALTERNATIVE_NODE, 16, 29, - new Node(TEXT_NODE, 16, 29, "blind mice ") - ) - ) - - ) - )); - } - - @Test - void alternationWithUnusedEndOptional() { - assertThat(astOf("three )blind\\ mice/rats"), equalTo( - new Node(EXPRESSION_NODE, 0, 23, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(ALTERNATION_NODE, 6, 23, - new Node(ALTERNATIVE_NODE, 6, 18, - new Node(TEXT_NODE, 6, 7, ")"), - new Node(TEXT_NODE, 7, 18, "blind mice") - ), - new Node(ALTERNATIVE_NODE, 19, 23, - new Node(TEXT_NODE, 19, 23, "rats") - ) - ) - ) - )); - } - - @Test - void alternationWithUnusedStartOptional() { - CucumberExpressionException exception = assertThrows( - CucumberExpressionException.class, - () -> astOf("three blind\\ mice/rats(")); - assertThat(exception.getMessage(), is("" + - "This Cucumber Expression has a problem at column 23:\n" + - "\n" + - "three blind\\ mice/rats(\n" + - " ^\n" + - "The '(' does not have a matching ')'.\n" + - "If you did not intend to use optional text you can use '\\(' to escape the optional text")); - } - - @Test - void alternationFollowedByOptional() { - assertThat(astOf("three blind\\ rat/cat(s)"), equalTo( - new Node(EXPRESSION_NODE, 0, 23, - new Node(TEXT_NODE, 0, 5, "three"), - new Node(TEXT_NODE, 5, 6, " "), - new Node(ALTERNATION_NODE, 6, 23, - new Node(ALTERNATIVE_NODE, 6, 16, - new Node(TEXT_NODE, 6, 16, "blind rat") - ), - new Node(ALTERNATIVE_NODE, 17, 23, - new Node(TEXT_NODE, 17, 20, "cat"), - new Node(OPTIONAL_NODE, 20, 23, - new Node(TEXT_NODE, 21, 22, "s") - ) - ) - ) - ) - )); - } - - String displayName; - - @BeforeEach - public void before(TestInfo testInfo){ - displayName = testInfo.getTestMethod().get().getName(); - } - - private Node astOf(String expression) { - Node parse = null; - RuntimeException runtimeException = null; - String message = null; - try { - parse = parser.parse(expression); - } catch (RuntimeException e) { - runtimeException = e; - message = e.getMessage(); - } - - Expectation expectation = new Expectation(expression, parse != null ? parse.toString() : null, message); - - Yaml yaml = new Yaml(); - String s = yaml.dumpAsMap(expectation); - - try { - Files.write(Paths.get("testdata", "ast", displayName + ".yaml" ), singletonList(s), StandardCharsets.UTF_8 ); - } catch (IOException e) { - throw new RuntimeException(e); - } - - if (runtimeException != null) { - throw runtimeException; - } - return parse; - } - - - private static DirectoryStream test() throws IOException { return newDirectoryStream(Paths.get("testdata", "ast")) ; } From a237f8932b2b604bcf73f8b58e86aa3191c38bf8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 21:42:20 +0200 Subject: [PATCH 101/183] Clean up test cases --- cucumber-expressions/java/testdata/ast/alternation.yaml | 1 - .../java/testdata/ast/alternationFollowedByOptional.yaml | 1 - cucumber-expressions/java/testdata/ast/alternationPhrase.yaml | 1 - .../java/testdata/ast/alternationWithUnusedEndOptional.yaml | 1 - .../java/testdata/ast/alternationWithUnusedStartOptional.yaml | 1 - .../java/testdata/ast/alternationWithWhiteSpace.yaml | 1 - cucumber-expressions/java/testdata/ast/anonymousParameter.yaml | 1 - cucumber-expressions/java/testdata/ast/closingBrace.yaml | 1 - cucumber-expressions/java/testdata/ast/closingParenthesis.yaml | 1 - cucumber-expressions/java/testdata/ast/emptyAlternation.yaml | 1 - cucumber-expressions/java/testdata/ast/emptyAlternations.yaml | 1 - cucumber-expressions/java/testdata/ast/emptyString.yaml | 1 - cucumber-expressions/java/testdata/ast/escapedAlternation.yaml | 1 - cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml | 1 - .../java/testdata/ast/escapedOpeningParenthesis.yaml | 1 - cucumber-expressions/java/testdata/ast/escapedOptional.yaml | 1 - .../java/testdata/ast/escapedOptionalFollowedByOptional.yaml | 1 - .../java/testdata/ast/escapedOptionalPhrase.yaml | 1 - cucumber-expressions/java/testdata/ast/openingBrace.yaml | 1 - cucumber-expressions/java/testdata/ast/openingParenthesis.yaml | 1 - cucumber-expressions/java/testdata/ast/optional.yaml | 1 - .../java/testdata/ast/optionalContainingEscapedOptional.yaml | 1 - cucumber-expressions/java/testdata/ast/optionalPhrase.yaml | 1 - cucumber-expressions/java/testdata/ast/parameter.yaml | 1 - cucumber-expressions/java/testdata/ast/phrase.yaml | 1 - cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml | 1 - 26 files changed, 26 deletions(-) diff --git a/cucumber-expressions/java/testdata/ast/alternation.yaml b/cucumber-expressions/java/testdata/ast/alternation.yaml index 9ab4a9c7da..7a48f14300 100644 --- a/cucumber-expressions/java/testdata/ast/alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation.yaml @@ -9,7 +9,6 @@ element: |- ]} ]} ]} -elements: null exception: null expression: mice/rats diff --git a/cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml b/cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml index 9ff543f3c8..6ccda786e9 100644 --- a/cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml @@ -14,7 +14,6 @@ element: |- ]} ]} ]} -elements: null exception: null expression: three blind\ rat/cat(s) diff --git a/cucumber-expressions/java/testdata/ast/alternationPhrase.yaml b/cucumber-expressions/java/testdata/ast/alternationPhrase.yaml index 75fcf3e151..9550a7c935 100644 --- a/cucumber-expressions/java/testdata/ast/alternationPhrase.yaml +++ b/cucumber-expressions/java/testdata/ast/alternationPhrase.yaml @@ -13,7 +13,6 @@ element: |- {type: 'TEXT_NODE', start: '18', end: 19', token: ' '} {type: 'TEXT_NODE', start: '19', end: 23', token: 'mice'} ]} -elements: null exception: null expression: three hungry/blind mice diff --git a/cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml b/cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml index e50e1d2b15..67a20314e9 100644 --- a/cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml @@ -12,7 +12,6 @@ element: |- ]} ]} ]} -elements: null exception: null expression: three )blind\ mice/rats diff --git a/cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml b/cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml index 3a6ede9811..6e6e455b0d 100644 --- a/cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml @@ -1,5 +1,4 @@ element: null -elements: null exception: |- This Cucumber Expression has a problem at column 23: diff --git a/cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml b/cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml index db1c920584..7d32ac0be9 100644 --- a/cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml +++ b/cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml @@ -9,7 +9,6 @@ element: |- ]} ]} ]} -elements: null exception: null expression: '\ three\ hungry/blind\ mice\ ' diff --git a/cucumber-expressions/java/testdata/ast/anonymousParameter.yaml b/cucumber-expressions/java/testdata/ast/anonymousParameter.yaml index 02aab824e2..e58056ee17 100644 --- a/cucumber-expressions/java/testdata/ast/anonymousParameter.yaml +++ b/cucumber-expressions/java/testdata/ast/anonymousParameter.yaml @@ -2,7 +2,6 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ {type: 'PARAMETER_NODE', start: '0', end: 2'} ]} -elements: null exception: null expression: '{}' diff --git a/cucumber-expressions/java/testdata/ast/closingBrace.yaml b/cucumber-expressions/java/testdata/ast/closingBrace.yaml index 26ce493661..b16e38ef04 100644 --- a/cucumber-expressions/java/testdata/ast/closingBrace.yaml +++ b/cucumber-expressions/java/testdata/ast/closingBrace.yaml @@ -2,7 +2,6 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ {type: 'TEXT_NODE', start: '0', end: 1', token: '}'} ]} -elements: null exception: null expression: '}' diff --git a/cucumber-expressions/java/testdata/ast/closingParenthesis.yaml b/cucumber-expressions/java/testdata/ast/closingParenthesis.yaml index 094fdd55cc..d9198a0a0f 100644 --- a/cucumber-expressions/java/testdata/ast/closingParenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/closingParenthesis.yaml @@ -2,7 +2,6 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ {type: 'TEXT_NODE', start: '0', end: 1', token: ')'} ]} -elements: null exception: null expression: ) diff --git a/cucumber-expressions/java/testdata/ast/emptyAlternation.yaml b/cucumber-expressions/java/testdata/ast/emptyAlternation.yaml index 892d214ecb..cb8972016f 100644 --- a/cucumber-expressions/java/testdata/ast/emptyAlternation.yaml +++ b/cucumber-expressions/java/testdata/ast/emptyAlternation.yaml @@ -5,7 +5,6 @@ element: |- {type: 'ALTERNATIVE_NODE', start: '1', end: 1'} ]} ]} -elements: null exception: null expression: / diff --git a/cucumber-expressions/java/testdata/ast/emptyAlternations.yaml b/cucumber-expressions/java/testdata/ast/emptyAlternations.yaml index 5acf31a948..48319b8023 100644 --- a/cucumber-expressions/java/testdata/ast/emptyAlternations.yaml +++ b/cucumber-expressions/java/testdata/ast/emptyAlternations.yaml @@ -6,7 +6,6 @@ element: |- {type: 'ALTERNATIVE_NODE', start: '2', end: 2'} ]} ]} -elements: null exception: null expression: // diff --git a/cucumber-expressions/java/testdata/ast/emptyString.yaml b/cucumber-expressions/java/testdata/ast/emptyString.yaml index ed6b1ec078..96a443e1e2 100644 --- a/cucumber-expressions/java/testdata/ast/emptyString.yaml +++ b/cucumber-expressions/java/testdata/ast/emptyString.yaml @@ -1,5 +1,4 @@ element: '{type: ''EXPRESSION_NODE'', start: ''0'', end: 0''}' -elements: null exception: null expression: '' diff --git a/cucumber-expressions/java/testdata/ast/escapedAlternation.yaml b/cucumber-expressions/java/testdata/ast/escapedAlternation.yaml index 3c445d3c0a..7448732fb0 100644 --- a/cucumber-expressions/java/testdata/ast/escapedAlternation.yaml +++ b/cucumber-expressions/java/testdata/ast/escapedAlternation.yaml @@ -2,7 +2,6 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 10', nodes: [ {type: 'TEXT_NODE', start: '0', end: 10', token: 'mice/rats'} ]} -elements: null exception: null expression: mice\/rats diff --git a/cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml b/cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml index b12a713b2b..1e107132ab 100644 --- a/cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml +++ b/cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml @@ -2,7 +2,6 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ {type: 'TEXT_NODE', start: '0', end: 2', token: '\'} ]} -elements: null exception: null expression: \\ diff --git a/cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml b/cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml index ad23873573..abe338676b 100644 --- a/cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml @@ -2,7 +2,6 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ {type: 'TEXT_NODE', start: '0', end: 2', token: '('} ]} -elements: null exception: null expression: \( diff --git a/cucumber-expressions/java/testdata/ast/escapedOptional.yaml b/cucumber-expressions/java/testdata/ast/escapedOptional.yaml index 7bd72d848f..e9dee33089 100644 --- a/cucumber-expressions/java/testdata/ast/escapedOptional.yaml +++ b/cucumber-expressions/java/testdata/ast/escapedOptional.yaml @@ -3,7 +3,6 @@ element: |- {type: 'TEXT_NODE', start: '0', end: 7', token: '(blind'} {type: 'TEXT_NODE', start: '7', end: 8', token: ')'} ]} -elements: null exception: null expression: \(blind) diff --git a/cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml b/cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml index 46c966211a..285c991cfe 100644 --- a/cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml +++ b/cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml @@ -12,7 +12,6 @@ element: |- {type: 'TEXT_NODE', start: '21', end: 22', token: ' '} {type: 'TEXT_NODE', start: '22', end: 26', token: 'mice'} ]} -elements: null exception: null expression: three \((very) blind) mice diff --git a/cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml b/cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml index da2da9dc81..c88a8419ed 100644 --- a/cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml +++ b/cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml @@ -7,7 +7,6 @@ element: |- {type: 'TEXT_NODE', start: '14', end: 15', token: ' '} {type: 'TEXT_NODE', start: '15', end: 19', token: 'mice'} ]} -elements: null exception: null expression: three \(blind) mice diff --git a/cucumber-expressions/java/testdata/ast/openingBrace.yaml b/cucumber-expressions/java/testdata/ast/openingBrace.yaml index b58491de63..134ae2b4d2 100644 --- a/cucumber-expressions/java/testdata/ast/openingBrace.yaml +++ b/cucumber-expressions/java/testdata/ast/openingBrace.yaml @@ -1,5 +1,4 @@ element: null -elements: null exception: |- This Cucumber Expression has a problem at column 1: diff --git a/cucumber-expressions/java/testdata/ast/openingParenthesis.yaml b/cucumber-expressions/java/testdata/ast/openingParenthesis.yaml index 52cf41e47e..779dc10da7 100644 --- a/cucumber-expressions/java/testdata/ast/openingParenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/openingParenthesis.yaml @@ -1,5 +1,4 @@ element: null -elements: null exception: |- This Cucumber Expression has a problem at column 1: diff --git a/cucumber-expressions/java/testdata/ast/optional.yaml b/cucumber-expressions/java/testdata/ast/optional.yaml index 0b81f4cd45..be30f5dda5 100644 --- a/cucumber-expressions/java/testdata/ast/optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional.yaml @@ -4,7 +4,6 @@ element: |- {type: 'TEXT_NODE', start: '1', end: 6', token: 'blind'} ]} ]} -elements: null exception: null expression: (blind) diff --git a/cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml b/cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml index d3cc971904..ec18b315fa 100644 --- a/cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml +++ b/cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml @@ -11,7 +11,6 @@ element: |- {type: 'TEXT_NODE', start: '21', end: 22', token: ' '} {type: 'TEXT_NODE', start: '22', end: 26', token: 'mice'} ]} -elements: null exception: null expression: three ((very\) blind) mice diff --git a/cucumber-expressions/java/testdata/ast/optionalPhrase.yaml b/cucumber-expressions/java/testdata/ast/optionalPhrase.yaml index 4d26badf92..0caab6f17b 100644 --- a/cucumber-expressions/java/testdata/ast/optionalPhrase.yaml +++ b/cucumber-expressions/java/testdata/ast/optionalPhrase.yaml @@ -8,7 +8,6 @@ element: |- {type: 'TEXT_NODE', start: '13', end: 14', token: ' '} {type: 'TEXT_NODE', start: '14', end: 18', token: 'mice'} ]} -elements: null exception: null expression: three (blind) mice diff --git a/cucumber-expressions/java/testdata/ast/parameter.yaml b/cucumber-expressions/java/testdata/ast/parameter.yaml index 84bd6358d8..3ae9d61682 100644 --- a/cucumber-expressions/java/testdata/ast/parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/parameter.yaml @@ -4,7 +4,6 @@ element: |- {type: 'TEXT_NODE', start: '1', end: 7', token: 'string'} ]} ]} -elements: null exception: null expression: '{string}' diff --git a/cucumber-expressions/java/testdata/ast/phrase.yaml b/cucumber-expressions/java/testdata/ast/phrase.yaml index 73fea9f321..042434a707 100644 --- a/cucumber-expressions/java/testdata/ast/phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/phrase.yaml @@ -6,7 +6,6 @@ element: |- {type: 'TEXT_NODE', start: '11', end: 12', token: ' '} {type: 'TEXT_NODE', start: '12', end: 16', token: 'mice'} ]} -elements: null exception: null expression: three blind mice diff --git a/cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml b/cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml index b1c9ac67f9..d6538c4eb1 100644 --- a/cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml +++ b/cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml @@ -1,5 +1,4 @@ element: null -elements: null exception: |- This Cucumber Expression has a problem at column 1: From ab865179126421dcb255d6c818d1b3d6ee22e39b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 21:45:28 +0200 Subject: [PATCH 102/183] Rename test cases --- ...lowedByOptional.yaml => alternation-followed-by-optional.yaml} | 0 .../ast/{alternationPhrase.yaml => alternation-phrase.yaml} | 0 ...EndOptional.yaml => alternation-with-unused-end-optional.yaml} | 0 ...tOptional.yaml => alternation-with-unused_start-optional.yaml} | 0 ...ationWithWhiteSpace.yaml => alternation-with-white-space.yaml} | 0 .../ast/{anonymousParameter.yaml => anonymous-parameter.yaml} | 0 .../java/testdata/ast/{closingBrace.yaml => closing-brace.yaml} | 0 .../ast/{closingParenthesis.yaml => closing-parenthesis.yaml} | 0 .../ast/{emptyAlternation.yaml => empty-alternation.yaml} | 0 .../ast/{emptyAlternations.yaml => empty-alternations.yaml} | 0 .../java/testdata/ast/{emptyString.yaml => empty-string.yaml} | 0 .../ast/{escapedAlternation.yaml => escaped-alternation.yaml} | 0 .../ast/{escapedBackSlash.yaml => escaped-backslash.yaml} | 0 ...edOpeningParenthesis.yaml => escaped-opening-parenthesis.yaml} | 0 ...ByOptional.yaml => escaped-optional-followed-by-optional.yaml} | 0 .../{escapedOptionalPhrase.yaml => escaped-optional-phrase.yaml} | 0 .../testdata/ast/{escapedOptional.yaml => escaped-optional.yaml} | 0 .../java/testdata/ast/{openingBrace.yaml => opening-brace.yaml} | 0 .../ast/{openingParenthesis.yaml => opening-parenthesis.yaml} | 0 ...pedOptional.yaml => optional-containing-escaped-optional.yaml} | 0 .../testdata/ast/{optionalPhrase.yaml => optional-phrase.yaml} | 0 .../ast/{unfinishedParameter.yaml => unfinished-parameter.yaml} | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename cucumber-expressions/java/testdata/ast/{alternationFollowedByOptional.yaml => alternation-followed-by-optional.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{alternationPhrase.yaml => alternation-phrase.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{alternationWithUnusedEndOptional.yaml => alternation-with-unused-end-optional.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{alternationWithUnusedStartOptional.yaml => alternation-with-unused_start-optional.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{alternationWithWhiteSpace.yaml => alternation-with-white-space.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{anonymousParameter.yaml => anonymous-parameter.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{closingBrace.yaml => closing-brace.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{closingParenthesis.yaml => closing-parenthesis.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{emptyAlternation.yaml => empty-alternation.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{emptyAlternations.yaml => empty-alternations.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{emptyString.yaml => empty-string.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{escapedAlternation.yaml => escaped-alternation.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{escapedBackSlash.yaml => escaped-backslash.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{escapedOpeningParenthesis.yaml => escaped-opening-parenthesis.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{escapedOptionalFollowedByOptional.yaml => escaped-optional-followed-by-optional.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{escapedOptionalPhrase.yaml => escaped-optional-phrase.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{escapedOptional.yaml => escaped-optional.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{openingBrace.yaml => opening-brace.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{openingParenthesis.yaml => opening-parenthesis.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{optionalContainingEscapedOptional.yaml => optional-containing-escaped-optional.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{optionalPhrase.yaml => optional-phrase.yaml} (100%) rename cucumber-expressions/java/testdata/ast/{unfinishedParameter.yaml => unfinished-parameter.yaml} (100%) diff --git a/cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/alternationFollowedByOptional.yaml rename to cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml diff --git a/cucumber-expressions/java/testdata/ast/alternationPhrase.yaml b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/alternationPhrase.yaml rename to cucumber-expressions/java/testdata/ast/alternation-phrase.yaml diff --git a/cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/alternationWithUnusedEndOptional.yaml rename to cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml diff --git a/cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/alternationWithUnusedStartOptional.yaml rename to cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml diff --git a/cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/alternationWithWhiteSpace.yaml rename to cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml diff --git a/cucumber-expressions/java/testdata/ast/anonymousParameter.yaml b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/anonymousParameter.yaml rename to cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml diff --git a/cucumber-expressions/java/testdata/ast/closingBrace.yaml b/cucumber-expressions/java/testdata/ast/closing-brace.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/closingBrace.yaml rename to cucumber-expressions/java/testdata/ast/closing-brace.yaml diff --git a/cucumber-expressions/java/testdata/ast/closingParenthesis.yaml b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/closingParenthesis.yaml rename to cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml diff --git a/cucumber-expressions/java/testdata/ast/emptyAlternation.yaml b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/emptyAlternation.yaml rename to cucumber-expressions/java/testdata/ast/empty-alternation.yaml diff --git a/cucumber-expressions/java/testdata/ast/emptyAlternations.yaml b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/emptyAlternations.yaml rename to cucumber-expressions/java/testdata/ast/empty-alternations.yaml diff --git a/cucumber-expressions/java/testdata/ast/emptyString.yaml b/cucumber-expressions/java/testdata/ast/empty-string.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/emptyString.yaml rename to cucumber-expressions/java/testdata/ast/empty-string.yaml diff --git a/cucumber-expressions/java/testdata/ast/escapedAlternation.yaml b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/escapedAlternation.yaml rename to cucumber-expressions/java/testdata/ast/escaped-alternation.yaml diff --git a/cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/escapedBackSlash.yaml rename to cucumber-expressions/java/testdata/ast/escaped-backslash.yaml diff --git a/cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/escapedOpeningParenthesis.yaml rename to cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml diff --git a/cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/escapedOptionalFollowedByOptional.yaml rename to cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml diff --git a/cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/escapedOptionalPhrase.yaml rename to cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml diff --git a/cucumber-expressions/java/testdata/ast/escapedOptional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/escapedOptional.yaml rename to cucumber-expressions/java/testdata/ast/escaped-optional.yaml diff --git a/cucumber-expressions/java/testdata/ast/openingBrace.yaml b/cucumber-expressions/java/testdata/ast/opening-brace.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/openingBrace.yaml rename to cucumber-expressions/java/testdata/ast/opening-brace.yaml diff --git a/cucumber-expressions/java/testdata/ast/openingParenthesis.yaml b/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/openingParenthesis.yaml rename to cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml diff --git a/cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/optionalContainingEscapedOptional.yaml rename to cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml diff --git a/cucumber-expressions/java/testdata/ast/optionalPhrase.yaml b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/optionalPhrase.yaml rename to cucumber-expressions/java/testdata/ast/optional-phrase.yaml diff --git a/cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml b/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/unfinishedParameter.yaml rename to cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml From cadf3f9bb2c903f41581b45a0d7cafe8306dabc7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 21:46:08 +0200 Subject: [PATCH 103/183] Clean up test cases --- .../java/testdata/ast/alternation-followed-by-optional.yaml | 1 - cucumber-expressions/java/testdata/ast/alternation-phrase.yaml | 1 - .../java/testdata/ast/alternation-with-unused-end-optional.yaml | 1 - .../java/testdata/ast/alternation-with-white-space.yaml | 1 - cucumber-expressions/java/testdata/ast/alternation.yaml | 1 - cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml | 1 - cucumber-expressions/java/testdata/ast/closing-brace.yaml | 1 - cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml | 1 - cucumber-expressions/java/testdata/ast/empty-alternation.yaml | 1 - cucumber-expressions/java/testdata/ast/empty-alternations.yaml | 1 - cucumber-expressions/java/testdata/ast/empty-string.yaml | 1 - cucumber-expressions/java/testdata/ast/escaped-alternation.yaml | 1 - cucumber-expressions/java/testdata/ast/escaped-backslash.yaml | 1 - .../java/testdata/ast/escaped-opening-parenthesis.yaml | 1 - .../java/testdata/ast/escaped-optional-followed-by-optional.yaml | 1 - .../java/testdata/ast/escaped-optional-phrase.yaml | 1 - cucumber-expressions/java/testdata/ast/escaped-optional.yaml | 1 - .../java/testdata/ast/optional-containing-escaped-optional.yaml | 1 - cucumber-expressions/java/testdata/ast/optional-phrase.yaml | 1 - cucumber-expressions/java/testdata/ast/optional.yaml | 1 - cucumber-expressions/java/testdata/ast/parameter.yaml | 1 - cucumber-expressions/java/testdata/ast/phrase.yaml | 1 - .../java/testdata/tokens/alternation-phrase.yaml | 1 - cucumber-expressions/java/testdata/tokens/alternation.yaml | 1 - cucumber-expressions/java/testdata/tokens/empty-string.yaml | 1 - .../tokens/escape-char-has-start-index-of-text-token.yaml | 1 - .../java/testdata/tokens/escaped-alternation.yaml | 1 - cucumber-expressions/java/testdata/tokens/escaped-optional.yaml | 1 - cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml | 1 - cucumber-expressions/java/testdata/tokens/escaped-space.yaml | 1 - cucumber-expressions/java/testdata/tokens/optional-phrase.yaml | 1 - cucumber-expressions/java/testdata/tokens/optional.yaml | 1 - cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml | 1 - cucumber-expressions/java/testdata/tokens/parameter.yaml | 1 - cucumber-expressions/java/testdata/tokens/phrase.yaml | 1 - 35 files changed, 35 deletions(-) diff --git a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml index 6ccda786e9..c1d4716a78 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml @@ -14,6 +14,5 @@ element: |- ]} ]} ]} -exception: null expression: three blind\ rat/cat(s) diff --git a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml index 9550a7c935..2aa149121d 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml @@ -13,6 +13,5 @@ element: |- {type: 'TEXT_NODE', start: '18', end: 19', token: ' '} {type: 'TEXT_NODE', start: '19', end: 23', token: 'mice'} ]} -exception: null expression: three hungry/blind mice diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml index 67a20314e9..603d87fbdf 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml @@ -12,6 +12,5 @@ element: |- ]} ]} ]} -exception: null expression: three )blind\ mice/rats diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml index 7d32ac0be9..ec2670c357 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml @@ -9,6 +9,5 @@ element: |- ]} ]} ]} -exception: null expression: '\ three\ hungry/blind\ mice\ ' diff --git a/cucumber-expressions/java/testdata/ast/alternation.yaml b/cucumber-expressions/java/testdata/ast/alternation.yaml index 7a48f14300..905130690f 100644 --- a/cucumber-expressions/java/testdata/ast/alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation.yaml @@ -9,6 +9,5 @@ element: |- ]} ]} ]} -exception: null expression: mice/rats diff --git a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml index e58056ee17..fc7ea48247 100644 --- a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml @@ -2,6 +2,5 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ {type: 'PARAMETER_NODE', start: '0', end: 2'} ]} -exception: null expression: '{}' diff --git a/cucumber-expressions/java/testdata/ast/closing-brace.yaml b/cucumber-expressions/java/testdata/ast/closing-brace.yaml index b16e38ef04..dccb401b90 100644 --- a/cucumber-expressions/java/testdata/ast/closing-brace.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-brace.yaml @@ -2,6 +2,5 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ {type: 'TEXT_NODE', start: '0', end: 1', token: '}'} ]} -exception: null expression: '}' diff --git a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml index d9198a0a0f..d6ba5bdabf 100644 --- a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml @@ -2,6 +2,5 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ {type: 'TEXT_NODE', start: '0', end: 1', token: ')'} ]} -exception: null expression: ) diff --git a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml index cb8972016f..0a9ac126c1 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml @@ -5,6 +5,5 @@ element: |- {type: 'ALTERNATIVE_NODE', start: '1', end: 1'} ]} ]} -exception: null expression: / diff --git a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml index 48319b8023..9ca48a2043 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml @@ -6,6 +6,5 @@ element: |- {type: 'ALTERNATIVE_NODE', start: '2', end: 2'} ]} ]} -exception: null expression: // diff --git a/cucumber-expressions/java/testdata/ast/empty-string.yaml b/cucumber-expressions/java/testdata/ast/empty-string.yaml index 96a443e1e2..2c41436715 100644 --- a/cucumber-expressions/java/testdata/ast/empty-string.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-string.yaml @@ -1,4 +1,3 @@ element: '{type: ''EXPRESSION_NODE'', start: ''0'', end: 0''}' -exception: null expression: '' diff --git a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml index 7448732fb0..adcc39b001 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml @@ -2,6 +2,5 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 10', nodes: [ {type: 'TEXT_NODE', start: '0', end: 10', token: 'mice/rats'} ]} -exception: null expression: mice\/rats diff --git a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml index 1e107132ab..483c4c1355 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml @@ -2,6 +2,5 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ {type: 'TEXT_NODE', start: '0', end: 2', token: '\'} ]} -exception: null expression: \\ diff --git a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml index abe338676b..c9e32c902d 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml @@ -2,6 +2,5 @@ element: |- {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ {type: 'TEXT_NODE', start: '0', end: 2', token: '('} ]} -exception: null expression: \( diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml index 285c991cfe..3aea660790 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -12,6 +12,5 @@ element: |- {type: 'TEXT_NODE', start: '21', end: 22', token: ' '} {type: 'TEXT_NODE', start: '22', end: 26', token: 'mice'} ]} -exception: null expression: three \((very) blind) mice diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml index c88a8419ed..9c00cfc85e 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml @@ -7,6 +7,5 @@ element: |- {type: 'TEXT_NODE', start: '14', end: 15', token: ' '} {type: 'TEXT_NODE', start: '15', end: 19', token: 'mice'} ]} -exception: null expression: three \(blind) mice diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml index e9dee33089..55829dc925 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml @@ -3,6 +3,5 @@ element: |- {type: 'TEXT_NODE', start: '0', end: 7', token: '(blind'} {type: 'TEXT_NODE', start: '7', end: 8', token: ')'} ]} -exception: null expression: \(blind) diff --git a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml index ec18b315fa..ae8806e794 100644 --- a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml @@ -11,6 +11,5 @@ element: |- {type: 'TEXT_NODE', start: '21', end: 22', token: ' '} {type: 'TEXT_NODE', start: '22', end: 26', token: 'mice'} ]} -exception: null expression: three ((very\) blind) mice diff --git a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml index 0caab6f17b..042f02c44a 100644 --- a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml @@ -8,6 +8,5 @@ element: |- {type: 'TEXT_NODE', start: '13', end: 14', token: ' '} {type: 'TEXT_NODE', start: '14', end: 18', token: 'mice'} ]} -exception: null expression: three (blind) mice diff --git a/cucumber-expressions/java/testdata/ast/optional.yaml b/cucumber-expressions/java/testdata/ast/optional.yaml index be30f5dda5..23dee55f53 100644 --- a/cucumber-expressions/java/testdata/ast/optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional.yaml @@ -4,6 +4,5 @@ element: |- {type: 'TEXT_NODE', start: '1', end: 6', token: 'blind'} ]} ]} -exception: null expression: (blind) diff --git a/cucumber-expressions/java/testdata/ast/parameter.yaml b/cucumber-expressions/java/testdata/ast/parameter.yaml index 3ae9d61682..46ab9295ec 100644 --- a/cucumber-expressions/java/testdata/ast/parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/parameter.yaml @@ -4,6 +4,5 @@ element: |- {type: 'TEXT_NODE', start: '1', end: 7', token: 'string'} ]} ]} -exception: null expression: '{string}' diff --git a/cucumber-expressions/java/testdata/ast/phrase.yaml b/cucumber-expressions/java/testdata/ast/phrase.yaml index 042434a707..285582415a 100644 --- a/cucumber-expressions/java/testdata/ast/phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/phrase.yaml @@ -6,6 +6,5 @@ element: |- {type: 'TEXT_NODE', start: '11', end: 12', token: ' '} {type: 'TEXT_NODE', start: '12', end: 16', token: 'mice'} ]} -exception: null expression: three blind mice diff --git a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml index c4086ef2dd..a69e10f824 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml @@ -1,4 +1,3 @@ -exception: null expression: three blind/cripple mice elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/alternation.yaml b/cucumber-expressions/java/testdata/tokens/alternation.yaml index f102d0fee2..a23738c3d0 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation.yaml @@ -1,4 +1,3 @@ -exception: null expression: blind/cripple elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/empty-string.yaml b/cucumber-expressions/java/testdata/tokens/empty-string.yaml index 7801ca7c42..1b0279bd9b 100644 --- a/cucumber-expressions/java/testdata/tokens/empty-string.yaml +++ b/cucumber-expressions/java/testdata/tokens/empty-string.yaml @@ -1,4 +1,3 @@ -exception: null expression: '' elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml index ad9649931d..15cd7914aa 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml @@ -1,4 +1,3 @@ -exception: null expression: ' \/ ' elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml index 4d2e67b372..6c4803947a 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml @@ -1,4 +1,3 @@ -exception: null expression: blind\ and\ famished\/cripple mice elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml index c16bc9b1be..f97ce701e9 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml @@ -1,4 +1,3 @@ -exception: null expression: \(blind\) elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml index 9f5bd674dc..f35b082e16 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml @@ -1,4 +1,3 @@ -exception: null expression: \{string\} elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml index 532cf8761f..21f8792bc4 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml @@ -1,4 +1,3 @@ -exception: null expression: '\ ' elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml index f14d693e89..f6cc0ee1a6 100644 --- a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml @@ -1,4 +1,3 @@ -exception: null expression: three (blind) mice elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/optional.yaml b/cucumber-expressions/java/testdata/tokens/optional.yaml index 189fe77eae..ccf9eab4fe 100644 --- a/cucumber-expressions/java/testdata/tokens/optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional.yaml @@ -1,4 +1,3 @@ -exception: null expression: (blind) elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml index 3a887fb0d1..1544aa714d 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml @@ -1,4 +1,3 @@ -exception: null expression: three {string} mice elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/parameter.yaml b/cucumber-expressions/java/testdata/tokens/parameter.yaml index 56722d488d..8bb733dc2f 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter.yaml @@ -1,4 +1,3 @@ -exception: null expression: '{string}' elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" diff --git a/cucumber-expressions/java/testdata/tokens/phrase.yaml b/cucumber-expressions/java/testdata/tokens/phrase.yaml index 9a1c504884..c443c6b39a 100644 --- a/cucumber-expressions/java/testdata/tokens/phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/phrase.yaml @@ -1,4 +1,3 @@ -exception: null expression: three blind mice elements: - "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" From 035e0a15d68bb7b3f56ae0079ad0bf092ac2ba10 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 22:01:48 +0200 Subject: [PATCH 104/183] Fix json for ast --- .../io/cucumber/cucumberexpressions/Ast.java | 12 +++++----- .../ast/alternation-followed-by-optional.yaml | 20 ++++++++--------- .../java/testdata/ast/alternation-phrase.yaml | 20 ++++++++--------- .../alternation-with-unused-end-optional.yaml | 18 +++++++-------- ...lternation-with-unused_start-optional.yaml | 5 ++--- .../ast/alternation-with-white-space.yaml | 12 +++++----- .../java/testdata/ast/alternation.yaml | 12 +++++----- .../testdata/ast/anonymous-parameter.yaml | 6 ++--- .../java/testdata/ast/closing-brace.yaml | 6 ++--- .../testdata/ast/closing-parenthesis.yaml | 4 ++-- .../java/testdata/ast/empty-alternation.yaml | 8 +++---- .../java/testdata/ast/empty-alternations.yaml | 12 +++++----- .../java/testdata/ast/empty-string.yaml | 5 +++-- .../testdata/ast/escaped-alternation.yaml | 7 +++--- .../java/testdata/ast/escaped-backslash.yaml | 7 +++--- .../ast/escaped-opening-parenthesis.yaml | 7 +++--- ...escaped-optional-followed-by-optional.yaml | 22 +++++++++---------- .../testdata/ast/escaped-optional-phrase.yaml | 14 ++++++------ .../java/testdata/ast/escaped-optional.yaml | 10 ++++----- .../java/testdata/ast/opening-brace.yaml | 7 +++--- .../testdata/ast/opening-parenthesis.yaml | 5 ++--- .../optional-containing-escaped-optional.yaml | 20 ++++++++--------- .../java/testdata/ast/optional-phrase.yaml | 14 ++++++------ .../java/testdata/ast/optional.yaml | 6 ++--- .../java/testdata/ast/parameter.yaml | 8 +++---- .../java/testdata/ast/phrase.yaml | 12 +++++----- .../testdata/ast/unfinished-parameter.yaml | 7 +++--- 27 files changed, 143 insertions(+), 143 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 68437b561a..93cf8a5aef 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -98,15 +98,15 @@ private StringBuilder toString(int depth) { sb.append(" "); } sb.append("{") - .append("type: '").append(type) - .append("', start: '") + .append("type: \"").append(type) + .append("\", start: \"") .append(start) - .append("', end: ") + .append("\", end: \"") .append(end) - .append("'"); + .append("\""); if (token != null) { - sb.append(", token: '").append(token).append("'"); + sb.append(", token: \"").append(token).append("\""); } if (nodes != null && !nodes.isEmpty()) { @@ -121,7 +121,7 @@ private StringBuilder toString(int depth) { sb.append("]"); } - sb.append('}'); + sb.append("}"); return sb; } diff --git a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml index c1d4716a78..d578d92ac1 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml @@ -1,15 +1,15 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 23', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} - {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} - {type: 'ALTERNATION_NODE', start: '6', end: 23', nodes: [ - {type: 'ALTERNATIVE_NODE', start: '6', end: 16', nodes: [ - {type: 'TEXT_NODE', start: '6', end: 16', token: 'blind rat'} + {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ + {type: "TEXT_NODE", start: "0", end: "5", token: "three"} + {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "ALTERNATION_NODE", start: "6", end: "23", nodes: [ + {type: "ALTERNATIVE_NODE", start: "6", end: "16", nodes: [ + {type: "TEXT_NODE", start: "6", end: "16", token: "blind rat"} ]} - {type: 'ALTERNATIVE_NODE', start: '17', end: 23', nodes: [ - {type: 'TEXT_NODE', start: '17', end: 20', token: 'cat'} - {type: 'OPTIONAL_NODE', start: '20', end: 23', nodes: [ - {type: 'TEXT_NODE', start: '21', end: 22', token: 's'} + {type: "ALTERNATIVE_NODE", start: "17", end: "23", nodes: [ + {type: "TEXT_NODE", start: "17", end: "20", token: "cat"} + {type: "OPTIONAL_NODE", start: "20", end: "23", nodes: [ + {type: "TEXT_NODE", start: "21", end: "22", token: "s"} ]} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml index 2aa149121d..1c09271f15 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml @@ -1,17 +1,17 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 23', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} - {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} - {type: 'ALTERNATION_NODE', start: '6', end: 18', nodes: [ - {type: 'ALTERNATIVE_NODE', start: '6', end: 12', nodes: [ - {type: 'TEXT_NODE', start: '6', end: 12', token: 'hungry'} + {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ + {type: "TEXT_NODE", start: "0", end: "5", token: "three"} + {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "ALTERNATION_NODE", start: "6", end: "18", nodes: [ + {type: "ALTERNATIVE_NODE", start: "6", end: "12", nodes: [ + {type: "TEXT_NODE", start: "6", end: "12", token: "hungry"} ]} - {type: 'ALTERNATIVE_NODE', start: '13', end: 18', nodes: [ - {type: 'TEXT_NODE', start: '13', end: 18', token: 'blind'} + {type: "ALTERNATIVE_NODE", start: "13", end: "18", nodes: [ + {type: "TEXT_NODE", start: "13", end: "18", token: "blind"} ]} ]} - {type: 'TEXT_NODE', start: '18', end: 19', token: ' '} - {type: 'TEXT_NODE', start: '19', end: 23', token: 'mice'} + {type: "TEXT_NODE", start: "18", end: "19", token: " "} + {type: "TEXT_NODE", start: "19", end: "23", token: "mice"} ]} expression: three hungry/blind mice diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml index 603d87fbdf..2ad5c6cc65 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml @@ -1,14 +1,14 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 23', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} - {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} - {type: 'ALTERNATION_NODE', start: '6', end: 23', nodes: [ - {type: 'ALTERNATIVE_NODE', start: '6', end: 18', nodes: [ - {type: 'TEXT_NODE', start: '6', end: 7', token: ')'} - {type: 'TEXT_NODE', start: '7', end: 18', token: 'blind mice'} + {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ + {type: "TEXT_NODE", start: "0", end: "5", token: "three"} + {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "ALTERNATION_NODE", start: "6", end: "23", nodes: [ + {type: "ALTERNATIVE_NODE", start: "6", end: "18", nodes: [ + {type: "TEXT_NODE", start: "6", end: "7", token: ")"} + {type: "TEXT_NODE", start: "7", end: "18", token: "blind mice"} ]} - {type: 'ALTERNATIVE_NODE', start: '19', end: 23', nodes: [ - {type: 'TEXT_NODE', start: '19', end: 23', token: 'rats'} + {type: "ALTERNATIVE_NODE", start: "19", end: "23", nodes: [ + {type: "TEXT_NODE", start: "19", end: "23", token: "rats"} ]} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml index 6e6e455b0d..b7cba7fab5 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml @@ -1,10 +1,9 @@ -element: null exception: |- This Cucumber Expression has a problem at column 23: three blind\ mice/rats( ^ - The '(' does not have a matching ')'. - If you did not intend to use optional text you can use '\(' to escape the optional text + The "(" does not have a matching ")". + If you did not intend to use optional text you can use "\(" to escape the optional text expression: three blind\ mice/rats( diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml index ec2670c357..b42b03c8bb 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml @@ -1,11 +1,11 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 29', nodes: [ - {type: 'ALTERNATION_NODE', start: '0', end: 29', nodes: [ - {type: 'ALTERNATIVE_NODE', start: '0', end: 15', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 15', token: ' three hungry'} + {type: "EXPRESSION_NODE", start: "0", end: "29", nodes: [ + {type: "ALTERNATION_NODE", start: "0", end: "29", nodes: [ + {type: "ALTERNATIVE_NODE", start: "0", end: "15", nodes: [ + {type: "TEXT_NODE", start: "0", end: "15", token: " three hungry"} ]} - {type: 'ALTERNATIVE_NODE', start: '16', end: 29', nodes: [ - {type: 'TEXT_NODE', start: '16', end: 29', token: 'blind mice '} + {type: "ALTERNATIVE_NODE", start: "16", end: "29", nodes: [ + {type: "TEXT_NODE", start: "16", end: "29", token: "blind mice "} ]} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation.yaml b/cucumber-expressions/java/testdata/ast/alternation.yaml index 905130690f..1bae32bb1a 100644 --- a/cucumber-expressions/java/testdata/ast/alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation.yaml @@ -1,11 +1,11 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 9', nodes: [ - {type: 'ALTERNATION_NODE', start: '0', end: 9', nodes: [ - {type: 'ALTERNATIVE_NODE', start: '0', end: 4', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 4', token: 'mice'} + {type: "EXPRESSION_NODE", start: "0", end: "9", nodes: [ + {type: "ALTERNATION_NODE", start: "0", end: "9", nodes: [ + {type: "ALTERNATIVE_NODE", start: "0", end: "4", nodes: [ + {type: "TEXT_NODE", start: "0", end: "4", token: "mice"} ]} - {type: 'ALTERNATIVE_NODE', start: '5', end: 9', nodes: [ - {type: 'TEXT_NODE', start: '5', end: 9', token: 'rats'} + {type: "ALTERNATIVE_NODE", start: "5", end: "9", nodes: [ + {type: "TEXT_NODE", start: "5", end: "9", token: "rats"} ]} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml index fc7ea48247..7e8e0f0712 100644 --- a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml @@ -1,6 +1,6 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ - {type: 'PARAMETER_NODE', start: '0', end: 2'} + {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ + {type: "PARAMETER_NODE", start: "0", end: "2"} ]} -expression: '{}' +expression: "{}" diff --git a/cucumber-expressions/java/testdata/ast/closing-brace.yaml b/cucumber-expressions/java/testdata/ast/closing-brace.yaml index dccb401b90..75e86add81 100644 --- a/cucumber-expressions/java/testdata/ast/closing-brace.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-brace.yaml @@ -1,6 +1,6 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 1', token: '}'} + {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ + {type: "TEXT_NODE", start: "0", end: "1", token: "}"} ]} -expression: '}' +expression: "}" diff --git a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml index d6ba5bdabf..7dfb1a9fe4 100644 --- a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml @@ -1,6 +1,6 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 1', token: ')'} + {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ + {type: "TEXT_NODE", start: "0", end: "1", token: ")"} ]} expression: ) diff --git a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml index 0a9ac126c1..e1706f46a3 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml @@ -1,8 +1,8 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 1', nodes: [ - {type: 'ALTERNATION_NODE', start: '0', end: 1', nodes: [ - {type: 'ALTERNATIVE_NODE', start: '0', end: 0'} - {type: 'ALTERNATIVE_NODE', start: '1', end: 1'} + {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ + {type: "ALTERNATION_NODE", start: "0", end: "1", nodes: [ + {type: "ALTERNATIVE_NODE", start: "0", end: "0"} + {type: "ALTERNATIVE_NODE", start: "1", end: "1"} ]} ]} expression: / diff --git a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml index 9ca48a2043..a393a16caf 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml @@ -1,10 +1,10 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ - {type: 'ALTERNATION_NODE', start: '0', end: 2', nodes: [ - {type: 'ALTERNATIVE_NODE', start: '0', end: 0'} - {type: 'ALTERNATIVE_NODE', start: '1', end: 1'} - {type: 'ALTERNATIVE_NODE', start: '2', end: 2'} + {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ + {type: "ALTERNATION_NODE", start: "0", end: "2", nodes: [ + {type: "ALTERNATIVE_NODE", start: "0", end: "0"} + {type: "ALTERNATIVE_NODE", start: "1", end: "1"} + {type: "ALTERNATIVE_NODE", start: "2", end: "2"} ]} ]} -expression: // +expression: '//' diff --git a/cucumber-expressions/java/testdata/ast/empty-string.yaml b/cucumber-expressions/java/testdata/ast/empty-string.yaml index 2c41436715..b76689999d 100644 --- a/cucumber-expressions/java/testdata/ast/empty-string.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-string.yaml @@ -1,3 +1,4 @@ -element: '{type: ''EXPRESSION_NODE'', start: ''0'', end: 0''}' -expression: '' +element: |- + {type: "EXPRESSION_NODE", start: "0", end: "0"} +expression: "" diff --git a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml index adcc39b001..7398b96a58 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml @@ -1,6 +1,7 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 10', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 10', token: 'mice/rats'} + {type: "EXPRESSION_NODE", start: "0", end: "10", nodes: [ + {type: "TEXT_NODE", start: "0", end: "10", token: "mice/rats"} ]} -expression: mice\/rats +expression: |- + mice\/rats diff --git a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml index 483c4c1355..2508789281 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml @@ -1,6 +1,7 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 2', token: '\'} + {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ + {type: "TEXT_NODE", start: "0", end: "2", token: "\"} ]} -expression: \\ +expression: |- + \\ diff --git a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml index c9e32c902d..958f8b9ac9 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml @@ -1,6 +1,7 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 2', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 2', token: '('} + {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ + {type: "TEXT_NODE", start: "0", end: "2", token: "("} ]} -expression: \( +expression: |- + \( diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml index 3aea660790..0b3f7e0fbd 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -1,16 +1,16 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 26', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} - {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} - {type: 'TEXT_NODE', start: '6', end: 8', token: '('} - {type: 'OPTIONAL_NODE', start: '8', end: 14', nodes: [ - {type: 'TEXT_NODE', start: '9', end: 13', token: 'very'} + {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ + {type: "TEXT_NODE", start: "0", end: "5", token: "three"} + {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "TEXT_NODE", start: "6", end: "8", token: "("} + {type: "OPTIONAL_NODE", start: "8", end: "14", nodes: [ + {type: "TEXT_NODE", start: "9", end: "13", token: "very"} ]} - {type: 'TEXT_NODE', start: '14', end: 15', token: ' '} - {type: 'TEXT_NODE', start: '15', end: 20', token: 'blind'} - {type: 'TEXT_NODE', start: '20', end: 21', token: ')'} - {type: 'TEXT_NODE', start: '21', end: 22', token: ' '} - {type: 'TEXT_NODE', start: '22', end: 26', token: 'mice'} + {type: "TEXT_NODE", start: "14", end: "15", token: " "} + {type: "TEXT_NODE", start: "15", end: "20", token: "blind"} + {type: "TEXT_NODE", start: "20", end: "21", token: ")"} + {type: "TEXT_NODE", start: "21", end: "22", token: " "} + {type: "TEXT_NODE", start: "22", end: "26", token: "mice"} ]} expression: three \((very) blind) mice diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml index 9c00cfc85e..ab48dc124d 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml @@ -1,11 +1,11 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 19', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} - {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} - {type: 'TEXT_NODE', start: '6', end: 13', token: '(blind'} - {type: 'TEXT_NODE', start: '13', end: 14', token: ')'} - {type: 'TEXT_NODE', start: '14', end: 15', token: ' '} - {type: 'TEXT_NODE', start: '15', end: 19', token: 'mice'} + {type: "EXPRESSION_NODE", start: "0", end: "19", nodes: [ + {type: "TEXT_NODE", start: "0", end: "5", token: "three"} + {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "TEXT_NODE", start: "6", end: "13", token: "(blind"} + {type: "TEXT_NODE", start: "13", end: "14", token: ")"} + {type: "TEXT_NODE", start: "14", end: "15", token: " "} + {type: "TEXT_NODE", start: "15", end: "19", token: "mice"} ]} expression: three \(blind) mice diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml index 55829dc925..95aac1c682 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml @@ -1,7 +1,7 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 8', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 7', token: '(blind'} - {type: 'TEXT_NODE', start: '7', end: 8', token: ')'} + {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ + {type: "TEXT_NODE", start: "0", end: "7", token: "(blind"} + {type: "TEXT_NODE", start: "7", end: "8", token: ")"} ]} -expression: \(blind) - +expression: |- + \(blind) diff --git a/cucumber-expressions/java/testdata/ast/opening-brace.yaml b/cucumber-expressions/java/testdata/ast/opening-brace.yaml index 134ae2b4d2..2a6acc465a 100644 --- a/cucumber-expressions/java/testdata/ast/opening-brace.yaml +++ b/cucumber-expressions/java/testdata/ast/opening-brace.yaml @@ -1,10 +1,9 @@ -element: null exception: |- This Cucumber Expression has a problem at column 1: { ^ - The '{' does not have a matching '}'. - If you did not intend to use a parameter you can use '\{' to escape the a parameter -expression: '{' + The "{" does not have a matching "}". + If you did not intend to use a parameter you can use "\{" to escape the a parameter +expression: "{" diff --git a/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml index 779dc10da7..9b620a3c2e 100644 --- a/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml @@ -1,10 +1,9 @@ -element: null exception: |- This Cucumber Expression has a problem at column 1: ( ^ - The '(' does not have a matching ')'. - If you did not intend to use optional text you can use '\(' to escape the optional text + The "(" does not have a matching ")". + If you did not intend to use optional text you can use "\(" to escape the optional text expression: ( diff --git a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml index ae8806e794..ddcafa5971 100644 --- a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml @@ -1,15 +1,15 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 26', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} - {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} - {type: 'OPTIONAL_NODE', start: '6', end: 21', nodes: [ - {type: 'TEXT_NODE', start: '7', end: 8', token: '('} - {type: 'TEXT_NODE', start: '8', end: 14', token: 'very)'} - {type: 'TEXT_NODE', start: '14', end: 15', token: ' '} - {type: 'TEXT_NODE', start: '15', end: 20', token: 'blind'} + {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ + {type: "TEXT_NODE", start: "0", end: "5", token: "three"} + {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "OPTIONAL_NODE", start: "6", end: "21", nodes: [ + {type: "TEXT_NODE", start: "7", end: "8", token: "("} + {type: "TEXT_NODE", start: "8", end: "14", token: "very)"} + {type: "TEXT_NODE", start: "14", end: "15", token: " "} + {type: "TEXT_NODE", start: "15", end: "20", token: "blind"} ]} - {type: 'TEXT_NODE', start: '21', end: 22', token: ' '} - {type: 'TEXT_NODE', start: '22', end: 26', token: 'mice'} + {type: "TEXT_NODE", start: "21", end: "22", token: " "} + {type: "TEXT_NODE", start: "22", end: "26", token: "mice"} ]} expression: three ((very\) blind) mice diff --git a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml index 042f02c44a..920fc59ad9 100644 --- a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml @@ -1,12 +1,12 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 18', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} - {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} - {type: 'OPTIONAL_NODE', start: '6', end: 13', nodes: [ - {type: 'TEXT_NODE', start: '7', end: 12', token: 'blind'} + {type: "EXPRESSION_NODE", start: "0", end: "18", nodes: [ + {type: "TEXT_NODE", start: "0", end: "5", token: "three"} + {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "OPTIONAL_NODE", start: "6", end: "13", nodes: [ + {type: "TEXT_NODE", start: "7", end: "12", token: "blind"} ]} - {type: 'TEXT_NODE', start: '13', end: 14', token: ' '} - {type: 'TEXT_NODE', start: '14', end: 18', token: 'mice'} + {type: "TEXT_NODE", start: "13", end: "14", token: " "} + {type: "TEXT_NODE", start: "14", end: "18", token: "mice"} ]} expression: three (blind) mice diff --git a/cucumber-expressions/java/testdata/ast/optional.yaml b/cucumber-expressions/java/testdata/ast/optional.yaml index 23dee55f53..bdf595aad3 100644 --- a/cucumber-expressions/java/testdata/ast/optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional.yaml @@ -1,7 +1,7 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 7', nodes: [ - {type: 'OPTIONAL_NODE', start: '0', end: 7', nodes: [ - {type: 'TEXT_NODE', start: '1', end: 6', token: 'blind'} + {type: "EXPRESSION_NODE", start: "0", end: "7", nodes: [ + {type: "OPTIONAL_NODE", start: "0", end: "7", nodes: [ + {type: "TEXT_NODE", start: "1", end: "6", token: "blind"} ]} ]} expression: (blind) diff --git a/cucumber-expressions/java/testdata/ast/parameter.yaml b/cucumber-expressions/java/testdata/ast/parameter.yaml index 46ab9295ec..acded2440e 100644 --- a/cucumber-expressions/java/testdata/ast/parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/parameter.yaml @@ -1,8 +1,8 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 8', nodes: [ - {type: 'PARAMETER_NODE', start: '0', end: 8', nodes: [ - {type: 'TEXT_NODE', start: '1', end: 7', token: 'string'} + {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ + {type: "PARAMETER_NODE", start: "0", end: "8", nodes: [ + {type: "TEXT_NODE", start: "1", end: "7", token: "string"} ]} ]} -expression: '{string}' +expression: "{string}" diff --git a/cucumber-expressions/java/testdata/ast/phrase.yaml b/cucumber-expressions/java/testdata/ast/phrase.yaml index 285582415a..c3b68355f3 100644 --- a/cucumber-expressions/java/testdata/ast/phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/phrase.yaml @@ -1,10 +1,10 @@ element: |- - {type: 'EXPRESSION_NODE', start: '0', end: 16', nodes: [ - {type: 'TEXT_NODE', start: '0', end: 5', token: 'three'} - {type: 'TEXT_NODE', start: '5', end: 6', token: ' '} - {type: 'TEXT_NODE', start: '6', end: 11', token: 'blind'} - {type: 'TEXT_NODE', start: '11', end: 12', token: ' '} - {type: 'TEXT_NODE', start: '12', end: 16', token: 'mice'} + {type: "EXPRESSION_NODE", start: "0", end: "16", nodes: [ + {type: "TEXT_NODE", start: "0", end: "5", token: "three"} + {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "TEXT_NODE", start: "6", end: "11", token: "blind"} + {type: "TEXT_NODE", start: "11", end: "12", token: " "} + {type: "TEXT_NODE", start: "12", end: "16", token: "mice"} ]} expression: three blind mice diff --git a/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml index d6538c4eb1..b7f0655bff 100644 --- a/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml @@ -1,10 +1,9 @@ -element: null exception: |- This Cucumber Expression has a problem at column 1: {string ^ - The '{' does not have a matching '}'. - If you did not intend to use a parameter you can use '\{' to escape the a parameter -expression: '{string' + The "{" does not have a matching "}". + If you did not intend to use a parameter you can use "\{" to escape the a parameter +expression: "{string" From 06179f1a4129f711f876330bc6185f45219118cf Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 22:15:33 +0200 Subject: [PATCH 105/183] Fix json for tokens --- .../io/cucumber/cucumberexpressions/Ast.java | 8 +++---- .../CucumberExpressionTokenizerTest.java | 6 ++--- .../cucumberexpressions/Expectation.java | 19 +-------------- .../testdata/tokens/alternation-phrase.yaml | 23 ++++++++++--------- .../java/testdata/tokens/alternation.yaml | 15 ++++++------ .../java/testdata/tokens/empty-string.yaml | 11 +++++---- ...pe-char-has-start-index-of-text-token.yaml | 15 ++++++------ .../tokens/escape-non-reserved-character.yaml | 10 ++++---- .../testdata/tokens/escaped-alternation.yaml | 15 ++++++------ .../testdata/tokens/escaped-end-of-line.yaml | 8 +++---- .../testdata/tokens/escaped-optional.yaml | 11 +++++---- .../testdata/tokens/escaped-parameter.yaml | 11 +++++---- .../java/testdata/tokens/escaped-space.yaml | 11 +++++---- .../java/testdata/tokens/optional-phrase.yaml | 23 ++++++++++--------- .../java/testdata/tokens/optional.yaml | 15 ++++++------ .../testdata/tokens/parameter-phrase.yaml | 23 ++++++++++--------- .../java/testdata/tokens/parameter.yaml | 17 +++++++------- .../java/testdata/tokens/phrase.yaml | 19 +++++++-------- 18 files changed, 126 insertions(+), 134 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 93cf8a5aef..97b53f3716 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -228,10 +228,10 @@ public int hashCode() { @Override public String toString() { return new StringJoiner(", ", "" + "{", "}") - .add("type: '" + type + "'") - .add("start: '" + start + "'") - .add("end: '" + end + "'") - .add("text: '" + text + "'") + .add("type: \"" + type + "\"") + .add("start: \"" + start + "\"") + .add("end: \"" + end + "\"") + .add("text: \"" + text + "\"") .toString(); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index dc3f8e4d87..4545a10bf2 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -29,12 +29,12 @@ private static DirectoryStream test() throws IOException { @MethodSource void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { if (expectation.getException() == null) { - List tokens = tokenizer + String tokens = tokenizer .tokenize(expectation.getExpression()) .stream() .map(Token::toString) - .collect(Collectors.toList()); - assertThat(tokens, is(expectation.getElements())); + .collect(Collectors.joining(",\n ", "[\n ","\n]")); + assertThat(tokens, is(expectation.getElement())); } else { CucumberExpressionException exception = assertThrows( CucumberExpressionException.class, diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java index 2ea93656d9..c71ff425a0 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java @@ -6,31 +6,19 @@ class Expectation { String expression; String element; - List elements; String exception; - private Expectation(String expression, String element, List elements, String exception) { + Expectation(String expression, String element, String exception) { this.expression = expression; this.element = element; - this.elements = elements; this.exception = exception; } - Expectation(String expression, List elements, String exception) { - this(expression, null, elements, exception); - } - - Expectation(String expression, String element, String exception) { - this(expression, element, null, exception); - } public String getExpression() { return expression; } - public List getElements() { - return elements; - } public String getException() { return exception; @@ -40,7 +28,6 @@ public static Expectation fromMap(Map map) { return new Expectation( (String) map.get("expression"), (String) map.get("element"), - (List) map.get("elements"), (String) map.get("exception")); } @@ -48,10 +35,6 @@ public void setExpression(String expression) { this.expression = expression; } - public void setElements(List elements) { - this.elements = elements; - } - public void setException(String exception) { this.exception = exception; } diff --git a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml index a69e10f824..7fcabc275d 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml @@ -1,12 +1,13 @@ expression: three blind/cripple mice -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '5', text: 'three'}" -- "{type: 'WHITE_SPACE', start: '5', end: '6', text: ' '}" -- "{type: 'TEXT', start: '6', end: '11', text: 'blind'}" -- "{type: 'ALTERNATION', start: '11', end: '12', text: '/'}" -- "{type: 'TEXT', start: '12', end: '19', text: 'cripple'}" -- "{type: 'WHITE_SPACE', start: '19', end: '20', text: ' '}" -- "{type: 'TEXT', start: '20', end: '24', text: 'mice'}" -- "{type: 'END_OF_LINE', start: '24', end: '24', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "5", text: "three"}, + {type: "WHITE_SPACE", start: "5", end: "6", text: " "}, + {type: "TEXT", start: "6", end: "11", text: "blind"}, + {type: "ALTERNATION", start: "11", end: "12", text: "/"}, + {type: "TEXT", start: "12", end: "19", text: "cripple"}, + {type: "WHITE_SPACE", start: "19", end: "20", text: " "}, + {type: "TEXT", start: "20", end: "24", text: "mice"}, + {type: "END_OF_LINE", start: "24", end: "24", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/alternation.yaml b/cucumber-expressions/java/testdata/tokens/alternation.yaml index a23738c3d0..7cfbcb76b2 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation.yaml @@ -1,8 +1,9 @@ expression: blind/cripple -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '5', text: 'blind'}" -- "{type: 'ALTERNATION', start: '5', end: '6', text: '/'}" -- "{type: 'TEXT', start: '6', end: '13', text: 'cripple'}" -- "{type: 'END_OF_LINE', start: '13', end: '13', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "5", text: "blind"}, + {type: "ALTERNATION", start: "5", end: "6", text: "/"}, + {type: "TEXT", start: "6", end: "13", text: "cripple"}, + {type: "END_OF_LINE", start: "13", end: "13", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/empty-string.yaml b/cucumber-expressions/java/testdata/tokens/empty-string.yaml index 1b0279bd9b..6ac284d35e 100644 --- a/cucumber-expressions/java/testdata/tokens/empty-string.yaml +++ b/cucumber-expressions/java/testdata/tokens/empty-string.yaml @@ -1,5 +1,6 @@ -expression: '' -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'END_OF_LINE', start: '0', end: '0', text: ''}" - +expression: "" +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "END_OF_LINE", start: "0", end: "0", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml index 15cd7914aa..1a181a5b51 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml @@ -1,8 +1,9 @@ expression: ' \/ ' -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'WHITE_SPACE', start: '0', end: '1', text: ' '}" -- "{type: 'TEXT', start: '1', end: '3', text: '/'}" -- "{type: 'WHITE_SPACE', start: '3', end: '4', text: ' '}" -- "{type: 'END_OF_LINE', start: '4', end: '4', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "WHITE_SPACE", start: "0", end: "1", text: " "}, + {type: "TEXT", start: "1", end: "3", text: "/"}, + {type: "WHITE_SPACE", start: "3", end: "4", text: " "}, + {type: "END_OF_LINE", start: "4", end: "4", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml index 4a136b8422..ea2998d895 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml @@ -1,10 +1,8 @@ +expression: \[ exception: |- This Cucumber Expression has a problem at column 2: \[ - ^ - Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. - If you did mean to use an '\' you can use '\\' to escape it -expression: \[ -elements: null - + ^ + Only the characters "{", "}", "(", ")", "\", "/" and whitespace can be escaped. + If you did mean to use an "\" you can use "\\" to escape it diff --git a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml index 6c4803947a..6841e6564d 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml @@ -1,8 +1,9 @@ expression: blind\ and\ famished\/cripple mice -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '29', text: 'blind and famished/cripple'}" -- "{type: 'WHITE_SPACE', start: '29', end: '30', text: ' '}" -- "{type: 'TEXT', start: '30', end: '34', text: 'mice'}" -- "{type: 'END_OF_LINE', start: '34', end: '34', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "29", text: "blind and famished/cripple"}, + {type: "WHITE_SPACE", start: "29", end: "30", text: " "}, + {type: "TEXT", start: "30", end: "34", text: "mice"}, + {type: "END_OF_LINE", start: "34", end: "34", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml index d625886802..4884e3d122 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml @@ -1,10 +1,8 @@ +expression: \ exception: |- This Cucumber Expression has a problem at column 2: \ - ^ + ^ The end of line can not be escaped. - You can use '\\' to escape the the '\' -expression: \ -elements: null - + You can use "\\" to escape the the "\" diff --git a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml index f97ce701e9..a1b46e023a 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml @@ -1,6 +1,7 @@ expression: \(blind\) -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '9', text: '(blind)'}" -- "{type: 'END_OF_LINE', start: '9', end: '9', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "9", text: "(blind)"}, + {type: "END_OF_LINE", start: "9", end: "9", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml index f35b082e16..04a3d8ea88 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml @@ -1,6 +1,7 @@ expression: \{string\} -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '10', text: '{string}'}" -- "{type: 'END_OF_LINE', start: '10', end: '10', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "10", text: "{string}"}, + {type: "END_OF_LINE", start: "10", end: "10", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml index 21f8792bc4..a9ee11e5f5 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml @@ -1,6 +1,7 @@ expression: '\ ' -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '2', text: ' '}" -- "{type: 'END_OF_LINE', start: '2', end: '2', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "2", text: " "}, + {type: "END_OF_LINE", start: "2", end: "2", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml index f6cc0ee1a6..8712a844ff 100644 --- a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml @@ -1,12 +1,13 @@ expression: three (blind) mice -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '5', text: 'three'}" -- "{type: 'WHITE_SPACE', start: '5', end: '6', text: ' '}" -- "{type: 'BEGIN_OPTIONAL', start: '6', end: '7', text: '('}" -- "{type: 'TEXT', start: '7', end: '12', text: 'blind'}" -- "{type: 'END_OPTIONAL', start: '12', end: '13', text: ')'}" -- "{type: 'WHITE_SPACE', start: '13', end: '14', text: ' '}" -- "{type: 'TEXT', start: '14', end: '18', text: 'mice'}" -- "{type: 'END_OF_LINE', start: '18', end: '18', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "5", text: "three"}, + {type: "WHITE_SPACE", start: "5", end: "6", text: " "}, + {type: "BEGIN_OPTIONAL", start: "6", end: "7", text: "("}, + {type: "TEXT", start: "7", end: "12", text: "blind"}, + {type: "END_OPTIONAL", start: "12", end: "13", text: ")"}, + {type: "WHITE_SPACE", start: "13", end: "14", text: " "}, + {type: "TEXT", start: "14", end: "18", text: "mice"}, + {type: "END_OF_LINE", start: "18", end: "18", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/optional.yaml b/cucumber-expressions/java/testdata/tokens/optional.yaml index ccf9eab4fe..e74bd990f5 100644 --- a/cucumber-expressions/java/testdata/tokens/optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional.yaml @@ -1,8 +1,9 @@ expression: (blind) -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'BEGIN_OPTIONAL', start: '0', end: '1', text: '('}" -- "{type: 'TEXT', start: '1', end: '6', text: 'blind'}" -- "{type: 'END_OPTIONAL', start: '6', end: '7', text: ')'}" -- "{type: 'END_OF_LINE', start: '7', end: '7', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "BEGIN_OPTIONAL", start: "0", end: "1", text: "("}, + {type: "TEXT", start: "1", end: "6", text: "blind"}, + {type: "END_OPTIONAL", start: "6", end: "7", text: ")"}, + {type: "END_OF_LINE", start: "7", end: "7", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml index 1544aa714d..388491a3a9 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml @@ -1,12 +1,13 @@ expression: three {string} mice -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '5', text: 'three'}" -- "{type: 'WHITE_SPACE', start: '5', end: '6', text: ' '}" -- "{type: 'BEGIN_PARAMETER', start: '6', end: '7', text: '{'}" -- "{type: 'TEXT', start: '7', end: '13', text: 'string'}" -- "{type: 'END_PARAMETER', start: '13', end: '14', text: '}'}" -- "{type: 'WHITE_SPACE', start: '14', end: '15', text: ' '}" -- "{type: 'TEXT', start: '15', end: '19', text: 'mice'}" -- "{type: 'END_OF_LINE', start: '19', end: '19', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "5", text: "three"}, + {type: "WHITE_SPACE", start: "5", end: "6", text: " "}, + {type: "BEGIN_PARAMETER", start: "6", end: "7", text: "{"}, + {type: "TEXT", start: "7", end: "13", text: "string"}, + {type: "END_PARAMETER", start: "13", end: "14", text: "}"}, + {type: "WHITE_SPACE", start: "14", end: "15", text: " "}, + {type: "TEXT", start: "15", end: "19", text: "mice"}, + {type: "END_OF_LINE", start: "19", end: "19", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/parameter.yaml b/cucumber-expressions/java/testdata/tokens/parameter.yaml index 8bb733dc2f..b13515c709 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter.yaml @@ -1,8 +1,9 @@ -expression: '{string}' -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'BEGIN_PARAMETER', start: '0', end: '1', text: '{'}" -- "{type: 'TEXT', start: '1', end: '7', text: 'string'}" -- "{type: 'END_PARAMETER', start: '7', end: '8', text: '}'}" -- "{type: 'END_OF_LINE', start: '8', end: '8', text: ''}" - +expression: "{string}" +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "BEGIN_PARAMETER", start: "0", end: "1", text: "{"}, + {type: "TEXT", start: "1", end: "7", text: "string"}, + {type: "END_PARAMETER", start: "7", end: "8", text: "}"}, + {type: "END_OF_LINE", start: "8", end: "8", text: ""} + ] diff --git a/cucumber-expressions/java/testdata/tokens/phrase.yaml b/cucumber-expressions/java/testdata/tokens/phrase.yaml index c443c6b39a..871ba32e94 100644 --- a/cucumber-expressions/java/testdata/tokens/phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/phrase.yaml @@ -1,10 +1,11 @@ expression: three blind mice -elements: -- "{type: 'START_OF_LINE', start: '0', end: '0', text: ''}" -- "{type: 'TEXT', start: '0', end: '5', text: 'three'}" -- "{type: 'WHITE_SPACE', start: '5', end: '6', text: ' '}" -- "{type: 'TEXT', start: '6', end: '11', text: 'blind'}" -- "{type: 'WHITE_SPACE', start: '11', end: '12', text: ' '}" -- "{type: 'TEXT', start: '12', end: '16', text: 'mice'}" -- "{type: 'END_OF_LINE', start: '16', end: '16', text: ''}" - +element: |- + [ + {type: "START_OF_LINE", start: "0", end: "0", text: ""}, + {type: "TEXT", start: "0", end: "5", text: "three"}, + {type: "WHITE_SPACE", start: "5", end: "6", text: " "}, + {type: "TEXT", start: "6", end: "11", text: "blind"}, + {type: "WHITE_SPACE", start: "11", end: "12", text: " "}, + {type: "TEXT", start: "12", end: "16", text: "mice"}, + {type: "END_OF_LINE", start: "16", end: "16", text: ""} + ] From d1f4fd1d15848fc32028411217580915e9fcbaa3 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 22:33:01 +0200 Subject: [PATCH 106/183] Fix json for ast --- .../io/cucumber/cucumberexpressions/Ast.java | 14 +++++------ .../CucumberExpressionParserTest.java | 23 ++----------------- .../ast/alternation-followed-by-optional.yaml | 8 +++---- .../java/testdata/ast/alternation-phrase.yaml | 10 ++++---- .../alternation-with-unused-end-optional.yaml | 8 +++---- .../ast/alternation-with-white-space.yaml | 2 +- .../java/testdata/ast/alternation.yaml | 2 +- .../java/testdata/ast/empty-alternation.yaml | 2 +- .../java/testdata/ast/empty-alternations.yaml | 4 ++-- ...escaped-optional-followed-by-optional.yaml | 16 ++++++------- .../testdata/ast/escaped-optional-phrase.yaml | 10 ++++---- .../java/testdata/ast/escaped-optional.yaml | 2 +- .../optional-containing-escaped-optional.yaml | 14 +++++------ .../java/testdata/ast/optional-phrase.yaml | 8 +++---- .../java/testdata/ast/phrase.yaml | 8 +++---- 15 files changed, 55 insertions(+), 76 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 97b53f3716..9237a6e264 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -110,17 +110,15 @@ private StringBuilder toString(int depth) { } if (nodes != null && !nodes.isEmpty()) { - sb.append(", nodes: [\n"); - for (Node node : nodes) { - sb.append(node.toString(depth + 1)); - sb.append("\n"); - } + sb.append(", nodes: "); + StringBuilder padding = new StringBuilder(); for (int i = 0; i < depth; i++) { - sb.append(" "); + padding.append(" "); } - sb.append("]"); + sb.append(nodes.stream() + .map(node -> node.toString(depth + 1)) + .collect(joining(",\n", "[\n", "\n" +padding + "]"))); } - sb.append("}"); return sb; } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index f9c2a47915..85642e0db9 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -1,36 +1,17 @@ package io.cucumber.cucumberexpressions; import io.cucumber.cucumberexpressions.Ast.Node; -import io.cucumber.cucumberexpressions.Ast.Token; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.provider.MethodSource; -import org.yaml.snakeyaml.Yaml; import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATION_NODE; -import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATIVE_NODE; -import static io.cucumber.cucumberexpressions.Ast.Node.Type.EXPRESSION_NODE; -import static io.cucumber.cucumberexpressions.Ast.Node.Type.OPTIONAL_NODE; -import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE; -import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE; + import static java.nio.file.Files.newDirectoryStream; -import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -39,7 +20,7 @@ class CucumberExpressionParserTest { private final CucumberExpressionParser parser = new CucumberExpressionParser(); private static DirectoryStream test() throws IOException { - return newDirectoryStream(Paths.get("testdata", "ast")) ; + return newDirectoryStream(Paths.get("testdata", "ast")); } @ParameterizedTest diff --git a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml index d578d92ac1..d849a92fa7 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml @@ -1,13 +1,13 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"} - {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, + {type: "TEXT_NODE", start: "5", end: "6", token: " "}, {type: "ALTERNATION_NODE", start: "6", end: "23", nodes: [ {type: "ALTERNATIVE_NODE", start: "6", end: "16", nodes: [ {type: "TEXT_NODE", start: "6", end: "16", token: "blind rat"} - ]} + ]}, {type: "ALTERNATIVE_NODE", start: "17", end: "23", nodes: [ - {type: "TEXT_NODE", start: "17", end: "20", token: "cat"} + {type: "TEXT_NODE", start: "17", end: "20", token: "cat"}, {type: "OPTIONAL_NODE", start: "20", end: "23", nodes: [ {type: "TEXT_NODE", start: "21", end: "22", token: "s"} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml index 1c09271f15..df72c307a9 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml @@ -1,16 +1,16 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"} - {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, + {type: "TEXT_NODE", start: "5", end: "6", token: " "}, {type: "ALTERNATION_NODE", start: "6", end: "18", nodes: [ {type: "ALTERNATIVE_NODE", start: "6", end: "12", nodes: [ {type: "TEXT_NODE", start: "6", end: "12", token: "hungry"} - ]} + ]}, {type: "ALTERNATIVE_NODE", start: "13", end: "18", nodes: [ {type: "TEXT_NODE", start: "13", end: "18", token: "blind"} ]} - ]} - {type: "TEXT_NODE", start: "18", end: "19", token: " "} + ]}, + {type: "TEXT_NODE", start: "18", end: "19", token: " "}, {type: "TEXT_NODE", start: "19", end: "23", token: "mice"} ]} expression: three hungry/blind mice diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml index 2ad5c6cc65..eeb39352ea 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml @@ -1,12 +1,12 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"} - {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, + {type: "TEXT_NODE", start: "5", end: "6", token: " "}, {type: "ALTERNATION_NODE", start: "6", end: "23", nodes: [ {type: "ALTERNATIVE_NODE", start: "6", end: "18", nodes: [ - {type: "TEXT_NODE", start: "6", end: "7", token: ")"} + {type: "TEXT_NODE", start: "6", end: "7", token: ")"}, {type: "TEXT_NODE", start: "7", end: "18", token: "blind mice"} - ]} + ]}, {type: "ALTERNATIVE_NODE", start: "19", end: "23", nodes: [ {type: "TEXT_NODE", start: "19", end: "23", token: "rats"} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml index b42b03c8bb..c494821487 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml @@ -3,7 +3,7 @@ element: |- {type: "ALTERNATION_NODE", start: "0", end: "29", nodes: [ {type: "ALTERNATIVE_NODE", start: "0", end: "15", nodes: [ {type: "TEXT_NODE", start: "0", end: "15", token: " three hungry"} - ]} + ]}, {type: "ALTERNATIVE_NODE", start: "16", end: "29", nodes: [ {type: "TEXT_NODE", start: "16", end: "29", token: "blind mice "} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation.yaml b/cucumber-expressions/java/testdata/ast/alternation.yaml index 1bae32bb1a..65bcce4d16 100644 --- a/cucumber-expressions/java/testdata/ast/alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation.yaml @@ -3,7 +3,7 @@ element: |- {type: "ALTERNATION_NODE", start: "0", end: "9", nodes: [ {type: "ALTERNATIVE_NODE", start: "0", end: "4", nodes: [ {type: "TEXT_NODE", start: "0", end: "4", token: "mice"} - ]} + ]}, {type: "ALTERNATIVE_NODE", start: "5", end: "9", nodes: [ {type: "TEXT_NODE", start: "5", end: "9", token: "rats"} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml index e1706f46a3..5f85deca05 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml @@ -1,7 +1,7 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "1", nodes: [ - {type: "ALTERNATIVE_NODE", start: "0", end: "0"} + {type: "ALTERNATIVE_NODE", start: "0", end: "0"}, {type: "ALTERNATIVE_NODE", start: "1", end: "1"} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml index a393a16caf..e57fa7f879 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml @@ -1,8 +1,8 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "2", nodes: [ - {type: "ALTERNATIVE_NODE", start: "0", end: "0"} - {type: "ALTERNATIVE_NODE", start: "1", end: "1"} + {type: "ALTERNATIVE_NODE", start: "0", end: "0"}, + {type: "ALTERNATIVE_NODE", start: "1", end: "1"}, {type: "ALTERNATIVE_NODE", start: "2", end: "2"} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml index 0b3f7e0fbd..c788dda28b 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -1,15 +1,15 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"} - {type: "TEXT_NODE", start: "5", end: "6", token: " "} - {type: "TEXT_NODE", start: "6", end: "8", token: "("} + {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, + {type: "TEXT_NODE", start: "5", end: "6", token: " "}, + {type: "TEXT_NODE", start: "6", end: "8", token: "("}, {type: "OPTIONAL_NODE", start: "8", end: "14", nodes: [ {type: "TEXT_NODE", start: "9", end: "13", token: "very"} - ]} - {type: "TEXT_NODE", start: "14", end: "15", token: " "} - {type: "TEXT_NODE", start: "15", end: "20", token: "blind"} - {type: "TEXT_NODE", start: "20", end: "21", token: ")"} - {type: "TEXT_NODE", start: "21", end: "22", token: " "} + ]}, + {type: "TEXT_NODE", start: "14", end: "15", token: " "}, + {type: "TEXT_NODE", start: "15", end: "20", token: "blind"}, + {type: "TEXT_NODE", start: "20", end: "21", token: ")"}, + {type: "TEXT_NODE", start: "21", end: "22", token: " "}, {type: "TEXT_NODE", start: "22", end: "26", token: "mice"} ]} expression: three \((very) blind) mice diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml index ab48dc124d..ccee67e463 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml @@ -1,10 +1,10 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "19", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"} - {type: "TEXT_NODE", start: "5", end: "6", token: " "} - {type: "TEXT_NODE", start: "6", end: "13", token: "(blind"} - {type: "TEXT_NODE", start: "13", end: "14", token: ")"} - {type: "TEXT_NODE", start: "14", end: "15", token: " "} + {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, + {type: "TEXT_NODE", start: "5", end: "6", token: " "}, + {type: "TEXT_NODE", start: "6", end: "13", token: "(blind"}, + {type: "TEXT_NODE", start: "13", end: "14", token: ")"}, + {type: "TEXT_NODE", start: "14", end: "15", token: " "}, {type: "TEXT_NODE", start: "15", end: "19", token: "mice"} ]} expression: three \(blind) mice diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml index 95aac1c682..05b43aec5c 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml @@ -1,6 +1,6 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ - {type: "TEXT_NODE", start: "0", end: "7", token: "(blind"} + {type: "TEXT_NODE", start: "0", end: "7", token: "(blind"}, {type: "TEXT_NODE", start: "7", end: "8", token: ")"} ]} expression: |- diff --git a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml index ddcafa5971..cf3195c92a 100644 --- a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml @@ -1,14 +1,14 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"} - {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, + {type: "TEXT_NODE", start: "5", end: "6", token: " "}, {type: "OPTIONAL_NODE", start: "6", end: "21", nodes: [ - {type: "TEXT_NODE", start: "7", end: "8", token: "("} - {type: "TEXT_NODE", start: "8", end: "14", token: "very)"} - {type: "TEXT_NODE", start: "14", end: "15", token: " "} + {type: "TEXT_NODE", start: "7", end: "8", token: "("}, + {type: "TEXT_NODE", start: "8", end: "14", token: "very)"}, + {type: "TEXT_NODE", start: "14", end: "15", token: " "}, {type: "TEXT_NODE", start: "15", end: "20", token: "blind"} - ]} - {type: "TEXT_NODE", start: "21", end: "22", token: " "} + ]}, + {type: "TEXT_NODE", start: "21", end: "22", token: " "}, {type: "TEXT_NODE", start: "22", end: "26", token: "mice"} ]} expression: three ((very\) blind) mice diff --git a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml index 920fc59ad9..9284825235 100644 --- a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml @@ -1,11 +1,11 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "18", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"} - {type: "TEXT_NODE", start: "5", end: "6", token: " "} + {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, + {type: "TEXT_NODE", start: "5", end: "6", token: " "}, {type: "OPTIONAL_NODE", start: "6", end: "13", nodes: [ {type: "TEXT_NODE", start: "7", end: "12", token: "blind"} - ]} - {type: "TEXT_NODE", start: "13", end: "14", token: " "} + ]}, + {type: "TEXT_NODE", start: "13", end: "14", token: " "}, {type: "TEXT_NODE", start: "14", end: "18", token: "mice"} ]} expression: three (blind) mice diff --git a/cucumber-expressions/java/testdata/ast/phrase.yaml b/cucumber-expressions/java/testdata/ast/phrase.yaml index c3b68355f3..15f83b968a 100644 --- a/cucumber-expressions/java/testdata/ast/phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/phrase.yaml @@ -1,9 +1,9 @@ element: |- {type: "EXPRESSION_NODE", start: "0", end: "16", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"} - {type: "TEXT_NODE", start: "5", end: "6", token: " "} - {type: "TEXT_NODE", start: "6", end: "11", token: "blind"} - {type: "TEXT_NODE", start: "11", end: "12", token: " "} + {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, + {type: "TEXT_NODE", start: "5", end: "6", token: " "}, + {type: "TEXT_NODE", start: "6", end: "11", token: "blind"}, + {type: "TEXT_NODE", start: "11", end: "12", token: " "}, {type: "TEXT_NODE", start: "12", end: "16", token: "mice"} ]} expression: three blind mice From 1f6d3cfc5d3a4d54317fa3e0df7bb49d4014a1dd Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 22:38:54 +0200 Subject: [PATCH 107/183] Clean up --- .../java/testdata/ast/alternation-followed-by-optional.yaml | 3 +-- .../java/testdata/ast/alternation-phrase.yaml | 3 +-- .../testdata/ast/alternation-with-unused-end-optional.yaml | 3 +-- .../testdata/ast/alternation-with-unused_start-optional.yaml | 3 +-- .../java/testdata/ast/alternation-with-white-space.yaml | 3 +-- cucumber-expressions/java/testdata/ast/alternation.yaml | 3 +-- .../java/testdata/ast/anonymous-parameter.yaml | 3 +-- cucumber-expressions/java/testdata/ast/closing-brace.yaml | 3 +-- .../java/testdata/ast/closing-parenthesis.yaml | 3 +-- cucumber-expressions/java/testdata/ast/empty-alternation.yaml | 3 +-- .../java/testdata/ast/empty-alternations.yaml | 3 +-- cucumber-expressions/java/testdata/ast/empty-string.yaml | 3 +-- .../java/testdata/ast/escaped-alternation.yaml | 4 +--- cucumber-expressions/java/testdata/ast/escaped-backslash.yaml | 4 +--- .../java/testdata/ast/escaped-opening-parenthesis.yaml | 4 +--- .../testdata/ast/escaped-optional-followed-by-optional.yaml | 3 +-- .../java/testdata/ast/escaped-optional-phrase.yaml | 3 +-- cucumber-expressions/java/testdata/ast/escaped-optional.yaml | 4 ++-- cucumber-expressions/java/testdata/ast/opening-brace.yaml | 3 +-- .../java/testdata/ast/opening-parenthesis.yaml | 3 +-- .../testdata/ast/optional-containing-escaped-optional.yaml | 3 +-- cucumber-expressions/java/testdata/ast/optional-phrase.yaml | 4 ++-- cucumber-expressions/java/testdata/ast/optional.yaml | 3 +-- cucumber-expressions/java/testdata/ast/parameter.yaml | 3 +-- cucumber-expressions/java/testdata/ast/phrase.yaml | 3 +-- .../java/testdata/ast/unfinished-parameter.yaml | 3 +-- 26 files changed, 28 insertions(+), 55 deletions(-) diff --git a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml index d849a92fa7..6d551c3445 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml @@ -1,3 +1,4 @@ +expression: three blind\ rat/cat(s) element: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, @@ -14,5 +15,3 @@ element: |- ]} ]} ]} -expression: three blind\ rat/cat(s) - diff --git a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml index df72c307a9..cb0814069b 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml @@ -1,3 +1,4 @@ +expression: three hungry/blind mice element: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, @@ -13,5 +14,3 @@ element: |- {type: "TEXT_NODE", start: "18", end: "19", token: " "}, {type: "TEXT_NODE", start: "19", end: "23", token: "mice"} ]} -expression: three hungry/blind mice - diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml index eeb39352ea..e88d6a978a 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml @@ -1,3 +1,4 @@ +expression: three )blind\ mice/rats element: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, @@ -12,5 +13,3 @@ element: |- ]} ]} ]} -expression: three )blind\ mice/rats - diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml index b7cba7fab5..f25fb0f6cb 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml @@ -1,3 +1,4 @@ +expression: three blind\ mice/rats( exception: |- This Cucumber Expression has a problem at column 23: @@ -5,5 +6,3 @@ exception: |- ^ The "(" does not have a matching ")". If you did not intend to use optional text you can use "\(" to escape the optional text -expression: three blind\ mice/rats( - diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml index c494821487..006cba9984 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml @@ -1,3 +1,4 @@ +expression: '\ three\ hungry/blind\ mice\ ' element: |- {type: "EXPRESSION_NODE", start: "0", end: "29", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "29", nodes: [ @@ -9,5 +10,3 @@ element: |- ]} ]} ]} -expression: '\ three\ hungry/blind\ mice\ ' - diff --git a/cucumber-expressions/java/testdata/ast/alternation.yaml b/cucumber-expressions/java/testdata/ast/alternation.yaml index 65bcce4d16..fc4fee716b 100644 --- a/cucumber-expressions/java/testdata/ast/alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation.yaml @@ -1,3 +1,4 @@ +expression: mice/rats element: |- {type: "EXPRESSION_NODE", start: "0", end: "9", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "9", nodes: [ @@ -9,5 +10,3 @@ element: |- ]} ]} ]} -expression: mice/rats - diff --git a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml index 7e8e0f0712..a3b9ad08b6 100644 --- a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml @@ -1,6 +1,5 @@ +expression: "{}" element: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "PARAMETER_NODE", start: "0", end: "2"} ]} -expression: "{}" - diff --git a/cucumber-expressions/java/testdata/ast/closing-brace.yaml b/cucumber-expressions/java/testdata/ast/closing-brace.yaml index 75e86add81..daa08ad7d0 100644 --- a/cucumber-expressions/java/testdata/ast/closing-brace.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-brace.yaml @@ -1,6 +1,5 @@ +expression: "}" element: |- {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ {type: "TEXT_NODE", start: "0", end: "1", token: "}"} ]} -expression: "}" - diff --git a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml index 7dfb1a9fe4..37f782fab2 100644 --- a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml @@ -1,6 +1,5 @@ +expression: ) element: |- {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ {type: "TEXT_NODE", start: "0", end: "1", token: ")"} ]} -expression: ) - diff --git a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml index 5f85deca05..6243b10b0c 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml @@ -1,3 +1,4 @@ +expression: / element: |- {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "1", nodes: [ @@ -5,5 +6,3 @@ element: |- {type: "ALTERNATIVE_NODE", start: "1", end: "1"} ]} ]} -expression: / - diff --git a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml index e57fa7f879..7bf01855b5 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml @@ -1,3 +1,4 @@ +expression: '//' element: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "2", nodes: [ @@ -6,5 +7,3 @@ element: |- {type: "ALTERNATIVE_NODE", start: "2", end: "2"} ]} ]} -expression: '//' - diff --git a/cucumber-expressions/java/testdata/ast/empty-string.yaml b/cucumber-expressions/java/testdata/ast/empty-string.yaml index b76689999d..e1ac51b813 100644 --- a/cucumber-expressions/java/testdata/ast/empty-string.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-string.yaml @@ -1,4 +1,3 @@ +expression: "" element: |- {type: "EXPRESSION_NODE", start: "0", end: "0"} -expression: "" - diff --git a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml index 7398b96a58..a3152c132b 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml @@ -1,7 +1,5 @@ +expression: 'mice\/rats' element: |- {type: "EXPRESSION_NODE", start: "0", end: "10", nodes: [ {type: "TEXT_NODE", start: "0", end: "10", token: "mice/rats"} ]} -expression: |- - mice\/rats - diff --git a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml index 2508789281..e4f5029e0f 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml @@ -1,7 +1,5 @@ +expression: '\\' element: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "TEXT_NODE", start: "0", end: "2", token: "\"} ]} -expression: |- - \\ - diff --git a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml index 958f8b9ac9..598d9782e8 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml @@ -1,7 +1,5 @@ +expression: '\(' element: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "TEXT_NODE", start: "0", end: "2", token: "("} ]} -expression: |- - \( - diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml index c788dda28b..8c55bc426b 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -1,3 +1,4 @@ +expression: three \((very) blind) mice element: |- {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, @@ -12,5 +13,3 @@ element: |- {type: "TEXT_NODE", start: "21", end: "22", token: " "}, {type: "TEXT_NODE", start: "22", end: "26", token: "mice"} ]} -expression: three \((very) blind) mice - diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml index ccee67e463..e6df16920e 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml @@ -1,3 +1,4 @@ +expression: three \(blind) mice element: |- {type: "EXPRESSION_NODE", start: "0", end: "19", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, @@ -7,5 +8,3 @@ element: |- {type: "TEXT_NODE", start: "14", end: "15", token: " "}, {type: "TEXT_NODE", start: "15", end: "19", token: "mice"} ]} -expression: three \(blind) mice - diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml index 05b43aec5c..3d3b5ac074 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml @@ -1,7 +1,7 @@ +expression: '\(blind)' + element: |- {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ {type: "TEXT_NODE", start: "0", end: "7", token: "(blind"}, {type: "TEXT_NODE", start: "7", end: "8", token: ")"} ]} -expression: |- - \(blind) diff --git a/cucumber-expressions/java/testdata/ast/opening-brace.yaml b/cucumber-expressions/java/testdata/ast/opening-brace.yaml index 2a6acc465a..2dc64afe10 100644 --- a/cucumber-expressions/java/testdata/ast/opening-brace.yaml +++ b/cucumber-expressions/java/testdata/ast/opening-brace.yaml @@ -1,3 +1,4 @@ +expression: "{" exception: |- This Cucumber Expression has a problem at column 1: @@ -5,5 +6,3 @@ exception: |- ^ The "{" does not have a matching "}". If you did not intend to use a parameter you can use "\{" to escape the a parameter -expression: "{" - diff --git a/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml index 9b620a3c2e..2f3dafc804 100644 --- a/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml @@ -1,3 +1,4 @@ +expression: ( exception: |- This Cucumber Expression has a problem at column 1: @@ -5,5 +6,3 @@ exception: |- ^ The "(" does not have a matching ")". If you did not intend to use optional text you can use "\(" to escape the optional text -expression: ( - diff --git a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml index cf3195c92a..29f5dc1155 100644 --- a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml @@ -1,3 +1,4 @@ +expression: three ((very\) blind) mice element: |- {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, @@ -11,5 +12,3 @@ element: |- {type: "TEXT_NODE", start: "21", end: "22", token: " "}, {type: "TEXT_NODE", start: "22", end: "26", token: "mice"} ]} -expression: three ((very\) blind) mice - diff --git a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml index 9284825235..42b4fbb9c8 100644 --- a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml @@ -1,3 +1,5 @@ +expression: three (blind) mice + element: |- {type: "EXPRESSION_NODE", start: "0", end: "18", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, @@ -8,5 +10,3 @@ element: |- {type: "TEXT_NODE", start: "13", end: "14", token: " "}, {type: "TEXT_NODE", start: "14", end: "18", token: "mice"} ]} -expression: three (blind) mice - diff --git a/cucumber-expressions/java/testdata/ast/optional.yaml b/cucumber-expressions/java/testdata/ast/optional.yaml index bdf595aad3..179ee9c8d6 100644 --- a/cucumber-expressions/java/testdata/ast/optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional.yaml @@ -1,8 +1,7 @@ +expression: (blind) element: |- {type: "EXPRESSION_NODE", start: "0", end: "7", nodes: [ {type: "OPTIONAL_NODE", start: "0", end: "7", nodes: [ {type: "TEXT_NODE", start: "1", end: "6", token: "blind"} ]} ]} -expression: (blind) - diff --git a/cucumber-expressions/java/testdata/ast/parameter.yaml b/cucumber-expressions/java/testdata/ast/parameter.yaml index acded2440e..44de6c9995 100644 --- a/cucumber-expressions/java/testdata/ast/parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/parameter.yaml @@ -1,8 +1,7 @@ +expression: "{string}" element: |- {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ {type: "PARAMETER_NODE", start: "0", end: "8", nodes: [ {type: "TEXT_NODE", start: "1", end: "7", token: "string"} ]} ]} -expression: "{string}" - diff --git a/cucumber-expressions/java/testdata/ast/phrase.yaml b/cucumber-expressions/java/testdata/ast/phrase.yaml index 15f83b968a..7595236cc7 100644 --- a/cucumber-expressions/java/testdata/ast/phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/phrase.yaml @@ -1,3 +1,4 @@ +expression: three blind mice element: |- {type: "EXPRESSION_NODE", start: "0", end: "16", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, @@ -6,5 +7,3 @@ element: |- {type: "TEXT_NODE", start: "11", end: "12", token: " "}, {type: "TEXT_NODE", start: "12", end: "16", token: "mice"} ]} -expression: three blind mice - diff --git a/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml index b7f0655bff..1688e2c690 100644 --- a/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml @@ -1,3 +1,4 @@ +expression: "{string" exception: |- This Cucumber Expression has a problem at column 1: @@ -5,5 +6,3 @@ exception: |- ^ The "{" does not have a matching "}". If you did not intend to use a parameter you can use "\{" to escape the a parameter -expression: "{string" - From f70310cff6ce797bc6025e840c05da7f07086e8d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 22:41:18 +0200 Subject: [PATCH 108/183] Clean up --- .../CucumberExpressionParserTest.java | 2 +- .../CucumberExpressionTokenizerTest.java | 3 +-- .../cucumberexpressions/Expectation.java | 17 ++++++++--------- .../ast/alternation-followed-by-optional.yaml | 2 +- .../java/testdata/ast/alternation-phrase.yaml | 2 +- .../alternation-with-unused-end-optional.yaml | 2 +- .../ast/alternation-with-white-space.yaml | 2 +- .../java/testdata/ast/alternation.yaml | 2 +- .../java/testdata/ast/anonymous-parameter.yaml | 2 +- .../java/testdata/ast/closing-brace.yaml | 2 +- .../java/testdata/ast/closing-parenthesis.yaml | 2 +- .../java/testdata/ast/empty-alternation.yaml | 2 +- .../java/testdata/ast/empty-alternations.yaml | 2 +- .../java/testdata/ast/empty-string.yaml | 2 +- .../java/testdata/ast/escaped-alternation.yaml | 2 +- .../java/testdata/ast/escaped-backslash.yaml | 2 +- .../ast/escaped-opening-parenthesis.yaml | 2 +- .../escaped-optional-followed-by-optional.yaml | 2 +- .../testdata/ast/escaped-optional-phrase.yaml | 2 +- .../java/testdata/ast/escaped-optional.yaml | 2 +- .../optional-containing-escaped-optional.yaml | 2 +- .../java/testdata/ast/optional-phrase.yaml | 2 +- .../java/testdata/ast/optional.yaml | 2 +- .../java/testdata/ast/parameter.yaml | 2 +- .../java/testdata/ast/phrase.yaml | 2 +- .../testdata/tokens/alternation-phrase.yaml | 2 +- .../java/testdata/tokens/alternation.yaml | 2 +- .../java/testdata/tokens/empty-string.yaml | 2 +- ...cape-char-has-start-index-of-text-token.yaml | 2 +- .../testdata/tokens/escaped-alternation.yaml | 2 +- .../java/testdata/tokens/escaped-optional.yaml | 2 +- .../java/testdata/tokens/escaped-parameter.yaml | 2 +- .../java/testdata/tokens/escaped-space.yaml | 2 +- .../java/testdata/tokens/optional-phrase.yaml | 2 +- .../java/testdata/tokens/optional.yaml | 2 +- .../java/testdata/tokens/parameter-phrase.yaml | 2 +- .../java/testdata/tokens/parameter.yaml | 2 +- .../java/testdata/tokens/phrase.yaml | 2 +- 38 files changed, 45 insertions(+), 47 deletions(-) diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 85642e0db9..9b57b57292 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -28,7 +28,7 @@ private static DirectoryStream test() throws IOException { void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { if (expectation.getException() == null) { Node node = parser.parse(expectation.getExpression()); - assertThat(node.toString(), is(expectation.getElement())); + assertThat(node.toString(), is(expectation.getExpected())); } else { CucumberExpressionException exception = assertThrows( CucumberExpressionException.class, diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 4545a10bf2..88266a6122 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -9,7 +9,6 @@ import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import java.util.stream.Collectors; import static java.nio.file.Files.newDirectoryStream; @@ -34,7 +33,7 @@ void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation .stream() .map(Token::toString) .collect(Collectors.joining(",\n ", "[\n ","\n]")); - assertThat(tokens, is(expectation.getElement())); + assertThat(tokens, is(expectation.getExpected())); } else { CucumberExpressionException exception = assertThrows( CucumberExpressionException.class, diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java index c71ff425a0..7a9e2c2e0a 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java @@ -1,16 +1,15 @@ package io.cucumber.cucumberexpressions; -import java.util.List; import java.util.Map; class Expectation { String expression; - String element; + String expected; String exception; - Expectation(String expression, String element, String exception) { + Expectation(String expression, String expected, String exception) { this.expression = expression; - this.element = element; + this.expected = expected; this.exception = exception; } @@ -27,7 +26,7 @@ public String getException() { public static Expectation fromMap(Map map) { return new Expectation( (String) map.get("expression"), - (String) map.get("element"), + (String) map.get("expected"), (String) map.get("exception")); } @@ -39,12 +38,12 @@ public void setException(String exception) { this.exception = exception; } - public String getElement() { - return element; + public String getExpected() { + return expected; } - public void setElement(String element) { - this.element = element; + public void setExpected(String expected) { + this.expected = expected; } } diff --git a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml index 6d551c3445..33c4174ed8 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml @@ -1,5 +1,5 @@ expression: three blind\ rat/cat(s) -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, {type: "TEXT_NODE", start: "5", end: "6", token: " "}, diff --git a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml index cb0814069b..d3435cae95 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml @@ -1,5 +1,5 @@ expression: three hungry/blind mice -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, {type: "TEXT_NODE", start: "5", end: "6", token: " "}, diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml index e88d6a978a..9b2ba46a41 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml @@ -1,5 +1,5 @@ expression: three )blind\ mice/rats -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, {type: "TEXT_NODE", start: "5", end: "6", token: " "}, diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml index 006cba9984..87d8287e6e 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml @@ -1,5 +1,5 @@ expression: '\ three\ hungry/blind\ mice\ ' -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "29", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "29", nodes: [ {type: "ALTERNATIVE_NODE", start: "0", end: "15", nodes: [ diff --git a/cucumber-expressions/java/testdata/ast/alternation.yaml b/cucumber-expressions/java/testdata/ast/alternation.yaml index fc4fee716b..6140d92f99 100644 --- a/cucumber-expressions/java/testdata/ast/alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation.yaml @@ -1,5 +1,5 @@ expression: mice/rats -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "9", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "9", nodes: [ {type: "ALTERNATIVE_NODE", start: "0", end: "4", nodes: [ diff --git a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml index a3b9ad08b6..b2110c7857 100644 --- a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml @@ -1,5 +1,5 @@ expression: "{}" -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "PARAMETER_NODE", start: "0", end: "2"} ]} diff --git a/cucumber-expressions/java/testdata/ast/closing-brace.yaml b/cucumber-expressions/java/testdata/ast/closing-brace.yaml index daa08ad7d0..43ec6e9d58 100644 --- a/cucumber-expressions/java/testdata/ast/closing-brace.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-brace.yaml @@ -1,5 +1,5 @@ expression: "}" -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ {type: "TEXT_NODE", start: "0", end: "1", token: "}"} ]} diff --git a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml index 37f782fab2..ba238056a6 100644 --- a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml @@ -1,5 +1,5 @@ expression: ) -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ {type: "TEXT_NODE", start: "0", end: "1", token: ")"} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml index 6243b10b0c..f6efb9a067 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml @@ -1,5 +1,5 @@ expression: / -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "1", nodes: [ {type: "ALTERNATIVE_NODE", start: "0", end: "0"}, diff --git a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml index 7bf01855b5..303a5784d8 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml @@ -1,5 +1,5 @@ expression: '//' -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "ALTERNATION_NODE", start: "0", end: "2", nodes: [ {type: "ALTERNATIVE_NODE", start: "0", end: "0"}, diff --git a/cucumber-expressions/java/testdata/ast/empty-string.yaml b/cucumber-expressions/java/testdata/ast/empty-string.yaml index e1ac51b813..1bcc08ccdf 100644 --- a/cucumber-expressions/java/testdata/ast/empty-string.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-string.yaml @@ -1,3 +1,3 @@ expression: "" -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "0"} diff --git a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml index a3152c132b..97d9b9bb81 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml @@ -1,5 +1,5 @@ expression: 'mice\/rats' -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "10", nodes: [ {type: "TEXT_NODE", start: "0", end: "10", token: "mice/rats"} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml index e4f5029e0f..ecf23265d5 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml @@ -1,5 +1,5 @@ expression: '\\' -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "TEXT_NODE", start: "0", end: "2", token: "\"} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml index 598d9782e8..01c02f8104 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml @@ -1,5 +1,5 @@ expression: '\(' -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ {type: "TEXT_NODE", start: "0", end: "2", token: "("} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml index 8c55bc426b..5cd048febc 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -1,5 +1,5 @@ expression: three \((very) blind) mice -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, {type: "TEXT_NODE", start: "5", end: "6", token: " "}, diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml index e6df16920e..4a063ff7f7 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml @@ -1,5 +1,5 @@ expression: three \(blind) mice -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "19", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, {type: "TEXT_NODE", start: "5", end: "6", token: " "}, diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml index 3d3b5ac074..22631abd12 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml @@ -1,6 +1,6 @@ expression: '\(blind)' -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ {type: "TEXT_NODE", start: "0", end: "7", token: "(blind"}, {type: "TEXT_NODE", start: "7", end: "8", token: ")"} diff --git a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml index 29f5dc1155..07593f290f 100644 --- a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml @@ -1,5 +1,5 @@ expression: three ((very\) blind) mice -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, {type: "TEXT_NODE", start: "5", end: "6", token: " "}, diff --git a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml index 42b4fbb9c8..3506153bf1 100644 --- a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml @@ -1,6 +1,6 @@ expression: three (blind) mice -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "18", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, {type: "TEXT_NODE", start: "5", end: "6", token: " "}, diff --git a/cucumber-expressions/java/testdata/ast/optional.yaml b/cucumber-expressions/java/testdata/ast/optional.yaml index 179ee9c8d6..65b2b6812d 100644 --- a/cucumber-expressions/java/testdata/ast/optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional.yaml @@ -1,5 +1,5 @@ expression: (blind) -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "7", nodes: [ {type: "OPTIONAL_NODE", start: "0", end: "7", nodes: [ {type: "TEXT_NODE", start: "1", end: "6", token: "blind"} diff --git a/cucumber-expressions/java/testdata/ast/parameter.yaml b/cucumber-expressions/java/testdata/ast/parameter.yaml index 44de6c9995..3f72778039 100644 --- a/cucumber-expressions/java/testdata/ast/parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/parameter.yaml @@ -1,5 +1,5 @@ expression: "{string}" -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ {type: "PARAMETER_NODE", start: "0", end: "8", nodes: [ {type: "TEXT_NODE", start: "1", end: "7", token: "string"} diff --git a/cucumber-expressions/java/testdata/ast/phrase.yaml b/cucumber-expressions/java/testdata/ast/phrase.yaml index 7595236cc7..66d452b66c 100644 --- a/cucumber-expressions/java/testdata/ast/phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/phrase.yaml @@ -1,5 +1,5 @@ expression: three blind mice -element: |- +expected: |- {type: "EXPRESSION_NODE", start: "0", end: "16", nodes: [ {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, {type: "TEXT_NODE", start: "5", end: "6", token: " "}, diff --git a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml index 7fcabc275d..70f4d4f7e4 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml @@ -1,5 +1,5 @@ expression: three blind/cripple mice -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "5", text: "three"}, diff --git a/cucumber-expressions/java/testdata/tokens/alternation.yaml b/cucumber-expressions/java/testdata/tokens/alternation.yaml index 7cfbcb76b2..f0b623d530 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation.yaml @@ -1,5 +1,5 @@ expression: blind/cripple -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "5", text: "blind"}, diff --git a/cucumber-expressions/java/testdata/tokens/empty-string.yaml b/cucumber-expressions/java/testdata/tokens/empty-string.yaml index 6ac284d35e..dd82c9b8cb 100644 --- a/cucumber-expressions/java/testdata/tokens/empty-string.yaml +++ b/cucumber-expressions/java/testdata/tokens/empty-string.yaml @@ -1,5 +1,5 @@ expression: "" -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "END_OF_LINE", start: "0", end: "0", text: ""} diff --git a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml index 1a181a5b51..516dffc3cf 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml @@ -1,5 +1,5 @@ expression: ' \/ ' -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "WHITE_SPACE", start: "0", end: "1", text: " "}, diff --git a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml index 6841e6564d..d56352e9e1 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml @@ -1,5 +1,5 @@ expression: blind\ and\ famished\/cripple mice -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "29", text: "blind and famished/cripple"}, diff --git a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml index a1b46e023a..1cb2a2e71d 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml @@ -1,5 +1,5 @@ expression: \(blind\) -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "9", text: "(blind)"}, diff --git a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml index 04a3d8ea88..b09505ee53 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml @@ -1,5 +1,5 @@ expression: \{string\} -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "10", text: "{string}"}, diff --git a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml index a9ee11e5f5..5a972a02e0 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml @@ -1,5 +1,5 @@ expression: '\ ' -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "2", text: " "}, diff --git a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml index 8712a844ff..928efe57c9 100644 --- a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml @@ -1,5 +1,5 @@ expression: three (blind) mice -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "5", text: "three"}, diff --git a/cucumber-expressions/java/testdata/tokens/optional.yaml b/cucumber-expressions/java/testdata/tokens/optional.yaml index e74bd990f5..8ec3f01c12 100644 --- a/cucumber-expressions/java/testdata/tokens/optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional.yaml @@ -1,5 +1,5 @@ expression: (blind) -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "BEGIN_OPTIONAL", start: "0", end: "1", text: "("}, diff --git a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml index 388491a3a9..2804961565 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml @@ -1,5 +1,5 @@ expression: three {string} mice -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "5", text: "three"}, diff --git a/cucumber-expressions/java/testdata/tokens/parameter.yaml b/cucumber-expressions/java/testdata/tokens/parameter.yaml index b13515c709..2372e4a058 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter.yaml @@ -1,5 +1,5 @@ expression: "{string}" -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "BEGIN_PARAMETER", start: "0", end: "1", text: "{"}, diff --git a/cucumber-expressions/java/testdata/tokens/phrase.yaml b/cucumber-expressions/java/testdata/tokens/phrase.yaml index 871ba32e94..37dd34194f 100644 --- a/cucumber-expressions/java/testdata/tokens/phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/phrase.yaml @@ -1,5 +1,5 @@ expression: three blind mice -element: |- +expected: |- [ {type: "START_OF_LINE", start: "0", end: "0", text: ""}, {type: "TEXT", start: "0", end: "5", text: "three"}, From 1382db4e3847083763782e5072d394fb14399cb4 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 10 Sep 2020 22:42:06 +0200 Subject: [PATCH 109/183] Clean up --- ...token.yaml => escaped-char-has-start-index-of-text-token.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cucumber-expressions/java/testdata/tokens/{escape-char-has-start-index-of-text-token.yaml => escaped-char-has-start-index-of-text-token.yaml} (100%) diff --git a/cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml b/cucumber-expressions/java/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml similarity index 100% rename from cucumber-expressions/java/testdata/tokens/escape-char-has-start-index-of-text-token.yaml rename to cucumber-expressions/java/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml From 9f2ab1902334c6fd16d067910e4209e573894008 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 11 Sep 2020 21:16:15 +0200 Subject: [PATCH 110/183] Golang json and yaml experiment (doesn't work) --- cucumber-expressions/go/ast.go | 8 +- .../go/cucumber_expression_parser.go | 14 +- .../go/cucumber_expression_tokenizer.go | 9 +- .../go/cucumber_expression_tokenizer_test.go | 149 ++++-------------- cucumber-expressions/go/go.mod | 1 + 5 files changed, 49 insertions(+), 132 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index 369ac13d40..7ce186a639 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -53,10 +53,10 @@ const ( ) type token struct { - text string - tokenType tokenType - start int - end int + Text string `json:"text"` + TokenType tokenType `json:"type"` + Start int `json:"start"` + End int `json:"end"` } var nullNode = node{textNode, -1, -1, "", []node{}} diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 53703a1362..e5c30b0900 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -5,7 +5,7 @@ package cucumberexpressions */ var textParser = func(tokens []token, current int) (int, node) { token := tokens[current] - return 1, node{textNode, token.start, token.end, token.text, []node{}} + return 1, node{textNode, token.Start, token.End, token.Text, []node{}} } /* @@ -36,7 +36,7 @@ var alternativeSeparatorParser = func(tokens []token, current int) (int, node) { return 0, nullNode } token := tokens[current] - return 1, node{alternativeNode, token.start, token.end, token.text, []node{}} + return 1, node{alternativeNode, token.Start, token.End, token.Text, []node{}} } var alternativeParsers = []parser{ @@ -72,8 +72,8 @@ var alternationParser = func(tokens []token, current int) (int, node) { } // Does not consume right hand boundary token - start := tokens[current].start - end := tokens[subCurrent].end + start := tokens[current].Start + end := tokens[subCurrent].End return consumed, node{alternationNode, start, end, "", splitAlternatives(start, end, subAst)} } @@ -120,8 +120,8 @@ func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, p return 0, nullNode } // consumes endToken - start := tokens[current].start - end := tokens[subCurrent].end + start := tokens[current].Start + end := tokens[subCurrent].End return subCurrent + 1 - current, node{nodeType, start, end, "", subAst} } } @@ -175,7 +175,7 @@ func lookingAt(tokens []token, at int, tokenType tokenType) bool { if at >= size { return tokenType == endOfLine } - return tokens[at].tokenType == tokenType + return tokens[at].TokenType == tokenType } func splitAlternatives(start int, end int, alternation []node) []node { diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 5c13789be6..65973eec60 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -1,12 +1,9 @@ package cucumberexpressions -import ( - //"regexp" - //"strings" -) - type tokenizer func(expression string, current int) (int, token) func tokenize(expression string) ([]token, error) { - return []token{}, nil + tokens := make([]token, 0) + + return tokens, nil } diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index 06f826ac00..1039d4bb49 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -1,130 +1,49 @@ package cucumberexpressions import ( + "encoding/json" + "fmt" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + "io/ioutil" "testing" ) -func TestCucumberExpressionTokenizer(t *testing.T) { - var assertContains = func(t *testing.T, expression string, expected []token) { - tokens, err := tokenize(expression) - require.NoError(t, err) - require.Equal(t, expected, tokens) - } - - t.Run("empty string", func(t *testing.T) { - assertContains(t, "", []token{ - {"", startOfLine, 0, 0}, - {"", endOfLine, 0, 0}, - }) - }) - t.Run("phrase", func(t *testing.T) { - assertContains(t, "three blind mice", []token{ - {"", startOfLine, -1, -1}, - {"three", text, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"blind", text, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"mice", text, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) - - t.Run("optional", func(t *testing.T) { - assertContains(t, "(blind)", []token{ - {"", startOfLine, -1, -1}, - {"(", beginOptional, -1, -1}, - {"blind", text, -1, -1}, - {")", endOptional, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) - - t.Run("escaped optional", func(t *testing.T) { - assertContains(t, "\\(blind\\)", []token{ - {"", startOfLine, -1, -1}, - {"(blind)", text, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) +type expectation struct { + Expression string `yaml:"expression"` + Expected string `yaml:"expected"` + Exception string `yaml:"exception"` +} - t.Run("optional phrase", func(t *testing.T) { - assertContains(t, "three (blind) mice", []token{ - {"", startOfLine, -1, -1}, - {"three", text, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"(", beginOptional, -1, -1}, - {"blind", text, -1, -1}, - {")", endOptional, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"mice", text, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) +func TestCucumberExpressionTokenizer(t *testing.T) { - t.Run("parameter", func(t *testing.T) { - assertContains(t, "{string}", []token{ - {"", startOfLine, -1, -1}, - {"{", beginParameter, -1, -1}, - {"string", text, -1, -1}, - {"}", endParameter, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) + //var assertContains = func(t *testing.T, expression string, expected []token) { + // tokens, err := tokenize(expression) + // require.NoError(t, err) + // require.Equal(t, expected, tokens) + //} - t.Run("escaped parameter", func(t *testing.T) { - assertContains(t, "\\{string\\}", []token{ - {"", startOfLine, -1, -1}, - {"{string}", text, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) + //t.Run("empty string", func(t *testing.T) { + // assertContains(t, "", []token{ + // {"", startOfLine, 0, 0}, + // {"", endOfLine, 0, 0}, + // }) + //}) - t.Run("parameter phrase", func(t *testing.T) { - assertContains(t, "three {string} mice", []token{ - {"", startOfLine, -1, -1}, - {"three", text, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"{", beginParameter, -1, -1}, - {"string", text, -1, -1}, - {"}", endParameter, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"mice", text, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) - t.Run("alternation", func(t *testing.T) { - assertContains(t, "blind/cripple", []token{ - {"", startOfLine, -1, -1}, - {"blind", text, -1, -1}, - {"/", alternation, -1, -1}, - {"cripple", text, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) + files, err := ioutil.ReadDir("./testdata/tokens") + require.NoError(t, err) - t.Run("escaped alternation", func(t *testing.T) { - assertContains(t, "blind\\ and\\ famished\\/cripple mice", []token{ - {"", startOfLine, -1, -1}, - {"blind and famished/cripple", text, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"mice", text, -1, -1}, - {"", endOfLine, -1, -1}, - }) - }) + for _, file := range files { + contents, err := ioutil.ReadFile("./testdata/tokens/" + file.Name()) + require.NoError(t, err) + t.Run(fmt.Sprintf("%s", file.Name()), func(t *testing.T) { + var expectation expectation + err = yaml.Unmarshal(contents, &expectation) + require.NoError(t, err) + var token []token + err = json.Unmarshal([]byte(expectation.Expected), &token) + require.NoError(t, err) - t.Run("alternation phrase", func(t *testing.T) { - assertContains(t, "three blind/cripple mice", []token{ - {"", startOfLine, -1, -1}, - {"three", text, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"blind", text, -1, -1}, - {"/", alternation, -1, -1}, - {"cripple", text, -1, -1}, - {" ", whiteSpace, -1, -1}, - {"mice", text, -1, -1}, - {"", endOfLine, -1, -1}, }) - }) - + } } diff --git a/cucumber-expressions/go/go.mod b/cucumber-expressions/go/go.mod index 5739432f95..b32bc03ea3 100644 --- a/cucumber-expressions/go/go.mod +++ b/cucumber-expressions/go/go.mod @@ -6,6 +6,7 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/stretchr/testify v1.6.1 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) go 1.13 From d49cde6ca850129dcc3a9feb1d42d2f4984d57a7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 11 Sep 2020 21:22:58 +0200 Subject: [PATCH 111/183] Json is hard --- .../io/cucumber/cucumberexpressions/Ast.java | 21 +++++++++--------- .../ast/alternation-followed-by-optional.yaml | 20 ++++++++--------- .../java/testdata/ast/alternation-phrase.yaml | 20 ++++++++--------- .../alternation-with-unused-end-optional.yaml | 18 +++++++-------- .../ast/alternation-with-white-space.yaml | 12 +++++----- .../java/testdata/ast/alternation.yaml | 12 +++++----- .../testdata/ast/anonymous-parameter.yaml | 4 ++-- .../java/testdata/ast/closing-brace.yaml | 4 ++-- .../testdata/ast/closing-parenthesis.yaml | 4 ++-- .../java/testdata/ast/empty-alternation.yaml | 8 +++---- .../java/testdata/ast/empty-alternations.yaml | 10 ++++----- .../java/testdata/ast/empty-string.yaml | 2 +- .../testdata/ast/escaped-alternation.yaml | 4 ++-- .../java/testdata/ast/escaped-backslash.yaml | 4 ++-- .../ast/escaped-opening-parenthesis.yaml | 4 ++-- ...escaped-optional-followed-by-optional.yaml | 22 +++++++++---------- .../testdata/ast/escaped-optional-phrase.yaml | 14 ++++++------ .../java/testdata/ast/escaped-optional.yaml | 6 ++--- .../optional-containing-escaped-optional.yaml | 20 ++++++++--------- .../java/testdata/ast/optional-phrase.yaml | 14 ++++++------ .../java/testdata/ast/optional.yaml | 6 ++--- .../java/testdata/ast/parameter.yaml | 6 ++--- .../java/testdata/ast/phrase.yaml | 12 +++++----- .../testdata/tokens/alternation-phrase.yaml | 18 +++++++-------- .../java/testdata/tokens/alternation.yaml | 10 ++++----- .../java/testdata/tokens/empty-string.yaml | 4 ++-- .../testdata/tokens/escaped-alternation.yaml | 10 ++++----- ...ed-char-has-start-index-of-text-token.yaml | 10 ++++----- .../testdata/tokens/escaped-optional.yaml | 6 ++--- .../testdata/tokens/escaped-parameter.yaml | 6 ++--- .../java/testdata/tokens/escaped-space.yaml | 6 ++--- .../java/testdata/tokens/optional-phrase.yaml | 18 +++++++-------- .../java/testdata/tokens/optional.yaml | 10 ++++----- .../testdata/tokens/parameter-phrase.yaml | 18 +++++++-------- .../java/testdata/tokens/parameter.yaml | 10 ++++----- .../java/testdata/tokens/phrase.yaml | 14 ++++++------ 36 files changed, 193 insertions(+), 194 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 9237a6e264..8fa4f4c0af 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -98,19 +98,18 @@ private StringBuilder toString(int depth) { sb.append(" "); } sb.append("{") - .append("type: \"").append(type) - .append("\", start: \"") + .append("\"type\": \"").append(type) + .append("\", \"start\": ") .append(start) - .append("\", end: \"") - .append(end) - .append("\""); + .append(", \"end\": ") + .append(end); if (token != null) { - sb.append(", token: \"").append(token).append("\""); + sb.append(", \"token\": \"").append(token).append("\""); } if (nodes != null && !nodes.isEmpty()) { - sb.append(", nodes: "); + sb.append(", \"nodes\": "); StringBuilder padding = new StringBuilder(); for (int i = 0; i < depth; i++) { padding.append(" "); @@ -226,10 +225,10 @@ public int hashCode() { @Override public String toString() { return new StringJoiner(", ", "" + "{", "}") - .add("type: \"" + type + "\"") - .add("start: \"" + start + "\"") - .add("end: \"" + end + "\"") - .add("text: \"" + text + "\"") + .add("\"type\": \"" + type + "\"") + .add("\"start\": " + start + "") + .add("\"end\": " + end + "") + .add("\"text\": \"" + text + "\"") .toString(); } diff --git a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml index 33c4174ed8..0bfb53a534 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-followed-by-optional.yaml @@ -1,16 +1,16 @@ expression: three blind\ rat/cat(s) expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, - {type: "TEXT_NODE", start: "5", end: "6", token: " "}, - {type: "ALTERNATION_NODE", start: "6", end: "23", nodes: [ - {type: "ALTERNATIVE_NODE", start: "6", end: "16", nodes: [ - {type: "TEXT_NODE", start: "6", end: "16", token: "blind rat"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 16, "token": "blind rat"} ]}, - {type: "ALTERNATIVE_NODE", start: "17", end: "23", nodes: [ - {type: "TEXT_NODE", start: "17", end: "20", token: "cat"}, - {type: "OPTIONAL_NODE", start: "20", end: "23", nodes: [ - {type: "TEXT_NODE", start: "21", end: "22", token: "s"} + {"type": "ALTERNATIVE_NODE", "start": 17, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 17, "end": 20, "token": "cat"}, + {"type": "OPTIONAL_NODE", "start": 20, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": "s"} ]} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml index d3435cae95..9a243822d5 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-phrase.yaml @@ -1,16 +1,16 @@ expression: three hungry/blind mice expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, - {type: "TEXT_NODE", start: "5", end: "6", token: " "}, - {type: "ALTERNATION_NODE", start: "6", end: "18", nodes: [ - {type: "ALTERNATIVE_NODE", start: "6", end: "12", nodes: [ - {type: "TEXT_NODE", start: "6", end: "12", token: "hungry"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 12, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 12, "token": "hungry"} ]}, - {type: "ALTERNATIVE_NODE", start: "13", end: "18", nodes: [ - {type: "TEXT_NODE", start: "13", end: "18", token: "blind"} + {"type": "ALTERNATIVE_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 13, "end": 18, "token": "blind"} ]} ]}, - {type: "TEXT_NODE", start: "18", end: "19", token: " "}, - {type: "TEXT_NODE", start: "19", end: "23", token: "mice"} + {"type": "TEXT_NODE", "start": 18, "end": 19, "token": " "}, + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "mice"} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml index 9b2ba46a41..842838b75f 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused-end-optional.yaml @@ -1,15 +1,15 @@ expression: three )blind\ mice/rats expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "23", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, - {type: "TEXT_NODE", start: "5", end: "6", token: " "}, - {type: "ALTERNATION_NODE", start: "6", end: "23", nodes: [ - {type: "ALTERNATIVE_NODE", start: "6", end: "18", nodes: [ - {type: "TEXT_NODE", start: "6", end: "7", token: ")"}, - {type: "TEXT_NODE", start: "7", end: "18", token: "blind mice"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 7, "token": ")"}, + {"type": "TEXT_NODE", "start": 7, "end": 18, "token": "blind mice"} ]}, - {type: "ALTERNATIVE_NODE", start: "19", end: "23", nodes: [ - {type: "TEXT_NODE", start: "19", end: "23", token: "rats"} + {"type": "ALTERNATIVE_NODE", "start": 19, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "rats"} ]} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml index 87d8287e6e..eedd57dd21 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-white-space.yaml @@ -1,12 +1,12 @@ expression: '\ three\ hungry/blind\ mice\ ' expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "29", nodes: [ - {type: "ALTERNATION_NODE", start: "0", end: "29", nodes: [ - {type: "ALTERNATIVE_NODE", start: "0", end: "15", nodes: [ - {type: "TEXT_NODE", start: "0", end: "15", token: " three hungry"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 15, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 15, "token": " three hungry"} ]}, - {type: "ALTERNATIVE_NODE", start: "16", end: "29", nodes: [ - {type: "TEXT_NODE", start: "16", end: "29", token: "blind mice "} + {"type": "ALTERNATIVE_NODE", "start": 16, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 16, "end": 29, "token": "blind mice "} ]} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation.yaml b/cucumber-expressions/java/testdata/ast/alternation.yaml index 6140d92f99..88df8325fe 100644 --- a/cucumber-expressions/java/testdata/ast/alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation.yaml @@ -1,12 +1,12 @@ expression: mice/rats expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "9", nodes: [ - {type: "ALTERNATION_NODE", start: "0", end: "9", nodes: [ - {type: "ALTERNATIVE_NODE", start: "0", end: "4", nodes: [ - {type: "TEXT_NODE", start: "0", end: "4", token: "mice"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 4, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 4, "token": "mice"} ]}, - {type: "ALTERNATIVE_NODE", start: "5", end: "9", nodes: [ - {type: "TEXT_NODE", start: "5", end: "9", token: "rats"} + {"type": "ALTERNATIVE_NODE", "start": 5, "end": 9, "nodes": [ + {"type": "TEXT_NODE", "start": 5, "end": 9, "token": "rats"} ]} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml index b2110c7857..940bd650f9 100644 --- a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml @@ -1,5 +1,5 @@ expression: "{}" expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ - {type: "PARAMETER_NODE", start: "0", end: "2"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 2} ]} diff --git a/cucumber-expressions/java/testdata/ast/closing-brace.yaml b/cucumber-expressions/java/testdata/ast/closing-brace.yaml index 43ec6e9d58..1bafd9c6a8 100644 --- a/cucumber-expressions/java/testdata/ast/closing-brace.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-brace.yaml @@ -1,5 +1,5 @@ expression: "}" expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ - {type: "TEXT_NODE", start: "0", end: "1", token: "}"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "}"} ]} diff --git a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml index ba238056a6..23daf7bcd3 100644 --- a/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/closing-parenthesis.yaml @@ -1,5 +1,5 @@ expression: ) expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ - {type: "TEXT_NODE", start: "0", end: "1", token: ")"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": ")"} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml index f6efb9a067..a7f5cbfa60 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml @@ -1,8 +1,8 @@ expression: / expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "1", nodes: [ - {type: "ALTERNATION_NODE", start: "0", end: "1", nodes: [ - {type: "ALTERNATIVE_NODE", start: "0", end: "0"}, - {type: "ALTERNATIVE_NODE", start: "1", end: "1"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml index 303a5784d8..90978553e4 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml @@ -1,9 +1,9 @@ expression: '//' expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ - {type: "ALTERNATION_NODE", start: "0", end: "2", nodes: [ - {type: "ALTERNATIVE_NODE", start: "0", end: "0"}, - {type: "ALTERNATIVE_NODE", start: "1", end: "1"}, - {type: "ALTERNATIVE_NODE", start: "2", end: "2"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1}, + {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-string.yaml b/cucumber-expressions/java/testdata/ast/empty-string.yaml index 1bcc08ccdf..4f1efe330c 100644 --- a/cucumber-expressions/java/testdata/ast/empty-string.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-string.yaml @@ -1,3 +1,3 @@ expression: "" expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "0"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 0} diff --git a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml index 97d9b9bb81..3ed9c37674 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-alternation.yaml @@ -1,5 +1,5 @@ expression: 'mice\/rats' expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "10", nodes: [ - {type: "TEXT_NODE", start: "0", end: "10", token: "mice/rats"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 10, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 10, "token": "mice/rats"} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml index ecf23265d5..af7b520dae 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml @@ -1,5 +1,5 @@ expression: '\\' expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ - {type: "TEXT_NODE", start: "0", end: "2", token: "\"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\"} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml index 01c02f8104..afafc59eb8 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-opening-parenthesis.yaml @@ -1,5 +1,5 @@ expression: '\(' expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "2", nodes: [ - {type: "TEXT_NODE", start: "0", end: "2", token: "("} + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "("} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml index 5cd048febc..1e4746291b 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -1,15 +1,15 @@ expression: three \((very) blind) mice expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, - {type: "TEXT_NODE", start: "5", end: "6", token: " "}, - {type: "TEXT_NODE", start: "6", end: "8", token: "("}, - {type: "OPTIONAL_NODE", start: "8", end: "14", nodes: [ - {type: "TEXT_NODE", start: "9", end: "13", token: "very"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 8, "token": "("}, + {"type": "OPTIONAL_NODE", "start": 8, "end": 14, "nodes": [ + {"type": "TEXT_NODE", "start": 9, "end": 13, "token": "very"} ]}, - {type: "TEXT_NODE", start: "14", end: "15", token: " "}, - {type: "TEXT_NODE", start: "15", end: "20", token: "blind"}, - {type: "TEXT_NODE", start: "20", end: "21", token: ")"}, - {type: "TEXT_NODE", start: "21", end: "22", token: " "}, - {type: "TEXT_NODE", start: "22", end: "26", token: "mice"} + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": ")"}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml index 4a063ff7f7..832249e2a7 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional-phrase.yaml @@ -1,10 +1,10 @@ expression: three \(blind) mice expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "19", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, - {type: "TEXT_NODE", start: "5", end: "6", token: " "}, - {type: "TEXT_NODE", start: "6", end: "13", token: "(blind"}, - {type: "TEXT_NODE", start: "13", end: "14", token: ")"}, - {type: "TEXT_NODE", start: "14", end: "15", token: " "}, - {type: "TEXT_NODE", start: "15", end: "19", token: "mice"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 19, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 13, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": ")"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 19, "token": "mice"} ]} diff --git a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml index 22631abd12..4c2b457d6f 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-optional.yaml @@ -1,7 +1,7 @@ expression: '\(blind)' expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ - {type: "TEXT_NODE", start: "0", end: "7", token: "(blind"}, - {type: "TEXT_NODE", start: "7", end: "8", token: ")"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 7, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": ")"} ]} diff --git a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml index 07593f290f..f09199a454 100644 --- a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml @@ -1,14 +1,14 @@ expression: three ((very\) blind) mice expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "26", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, - {type: "TEXT_NODE", start: "5", end: "6", token: " "}, - {type: "OPTIONAL_NODE", start: "6", end: "21", nodes: [ - {type: "TEXT_NODE", start: "7", end: "8", token: "("}, - {type: "TEXT_NODE", start: "8", end: "14", token: "very)"}, - {type: "TEXT_NODE", start: "14", end: "15", token: " "}, - {type: "TEXT_NODE", start: "15", end: "20", token: "blind"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, + {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} ]}, - {type: "TEXT_NODE", start: "21", end: "22", token: " "}, - {type: "TEXT_NODE", start: "22", end: "26", token: "mice"} + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} ]} diff --git a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml index 3506153bf1..0ef4fb3f95 100644 --- a/cucumber-expressions/java/testdata/ast/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/optional-phrase.yaml @@ -1,12 +1,12 @@ expression: three (blind) mice expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "18", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, - {type: "TEXT_NODE", start: "5", end: "6", token: " "}, - {type: "OPTIONAL_NODE", start: "6", end: "13", nodes: [ - {type: "TEXT_NODE", start: "7", end: "12", token: "blind"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 12, "token": "blind"} ]}, - {type: "TEXT_NODE", start: "13", end: "14", token: " "}, - {type: "TEXT_NODE", start: "14", end: "18", token: "mice"} + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 18, "token": "mice"} ]} diff --git a/cucumber-expressions/java/testdata/ast/optional.yaml b/cucumber-expressions/java/testdata/ast/optional.yaml index 65b2b6812d..6ce2b632e7 100644 --- a/cucumber-expressions/java/testdata/ast/optional.yaml +++ b/cucumber-expressions/java/testdata/ast/optional.yaml @@ -1,7 +1,7 @@ expression: (blind) expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "7", nodes: [ - {type: "OPTIONAL_NODE", start: "0", end: "7", nodes: [ - {type: "TEXT_NODE", start: "1", end: "6", token: "blind"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 6, "token": "blind"} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/parameter.yaml b/cucumber-expressions/java/testdata/ast/parameter.yaml index 3f72778039..92ac8c147e 100644 --- a/cucumber-expressions/java/testdata/ast/parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/parameter.yaml @@ -1,7 +1,7 @@ expression: "{string}" expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "8", nodes: [ - {type: "PARAMETER_NODE", start: "0", end: "8", nodes: [ - {type: "TEXT_NODE", start: "1", end: "7", token: "string"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 7, "token": "string"} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/phrase.yaml b/cucumber-expressions/java/testdata/ast/phrase.yaml index 66d452b66c..ba340d0122 100644 --- a/cucumber-expressions/java/testdata/ast/phrase.yaml +++ b/cucumber-expressions/java/testdata/ast/phrase.yaml @@ -1,9 +1,9 @@ expression: three blind mice expected: |- - {type: "EXPRESSION_NODE", start: "0", end: "16", nodes: [ - {type: "TEXT_NODE", start: "0", end: "5", token: "three"}, - {type: "TEXT_NODE", start: "5", end: "6", token: " "}, - {type: "TEXT_NODE", start: "6", end: "11", token: "blind"}, - {type: "TEXT_NODE", start: "11", end: "12", token: " "}, - {type: "TEXT_NODE", start: "12", end: "16", token: "mice"} + {"type": "EXPRESSION_NODE", "start": 0, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 11, "token": "blind"}, + {"type": "TEXT_NODE", "start": 11, "end": 12, "token": " "}, + {"type": "TEXT_NODE", "start": 12, "end": 16, "token": "mice"} ]} diff --git a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml index 70f4d4f7e4..48b107f64e 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation-phrase.yaml @@ -1,13 +1,13 @@ expression: three blind/cripple mice expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "5", text: "three"}, - {type: "WHITE_SPACE", start: "5", end: "6", text: " "}, - {type: "TEXT", start: "6", end: "11", text: "blind"}, - {type: "ALTERNATION", start: "11", end: "12", text: "/"}, - {type: "TEXT", start: "12", end: "19", text: "cripple"}, - {type: "WHITE_SPACE", start: "19", end: "20", text: " "}, - {type: "TEXT", start: "20", end: "24", text: "mice"}, - {type: "END_OF_LINE", start: "24", end: "24", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "ALTERNATION", "start": 11, "end": 12, "text": "/"}, + {"type": "TEXT", "start": 12, "end": 19, "text": "cripple"}, + {"type": "WHITE_SPACE", "start": 19, "end": 20, "text": " "}, + {"type": "TEXT", "start": 20, "end": 24, "text": "mice"}, + {"type": "END_OF_LINE", "start": 24, "end": 24, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/alternation.yaml b/cucumber-expressions/java/testdata/tokens/alternation.yaml index f0b623d530..a4920f22e5 100644 --- a/cucumber-expressions/java/testdata/tokens/alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/alternation.yaml @@ -1,9 +1,9 @@ expression: blind/cripple expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "5", text: "blind"}, - {type: "ALTERNATION", start: "5", end: "6", text: "/"}, - {type: "TEXT", start: "6", end: "13", text: "cripple"}, - {type: "END_OF_LINE", start: "13", end: "13", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "blind"}, + {"type": "ALTERNATION", "start": 5, "end": 6, "text": "/"}, + {"type": "TEXT", "start": 6, "end": 13, "text": "cripple"}, + {"type": "END_OF_LINE", "start": 13, "end": 13, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/empty-string.yaml b/cucumber-expressions/java/testdata/tokens/empty-string.yaml index dd82c9b8cb..501f7522f2 100644 --- a/cucumber-expressions/java/testdata/tokens/empty-string.yaml +++ b/cucumber-expressions/java/testdata/tokens/empty-string.yaml @@ -1,6 +1,6 @@ expression: "" expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "END_OF_LINE", start: "0", end: "0", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "END_OF_LINE", "start": 0, "end": 0, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml index d56352e9e1..7e21f7ad19 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-alternation.yaml @@ -1,9 +1,9 @@ expression: blind\ and\ famished\/cripple mice expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "29", text: "blind and famished/cripple"}, - {type: "WHITE_SPACE", start: "29", end: "30", text: " "}, - {type: "TEXT", start: "30", end: "34", text: "mice"}, - {type: "END_OF_LINE", start: "34", end: "34", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 29, "text": "blind and famished/cripple"}, + {"type": "WHITE_SPACE", "start": 29, "end": 30, "text": " "}, + {"type": "TEXT", "start": 30, "end": 34, "text": "mice"}, + {"type": "END_OF_LINE", "start": 34, "end": 34, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml b/cucumber-expressions/java/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml index 516dffc3cf..6375ad52a5 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml @@ -1,9 +1,9 @@ expression: ' \/ ' expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "WHITE_SPACE", start: "0", end: "1", text: " "}, - {type: "TEXT", start: "1", end: "3", text: "/"}, - {type: "WHITE_SPACE", start: "3", end: "4", text: " "}, - {type: "END_OF_LINE", start: "4", end: "4", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "WHITE_SPACE", "start": 0, "end": 1, "text": " "}, + {"type": "TEXT", "start": 1, "end": 3, "text": "/"}, + {"type": "WHITE_SPACE", "start": 3, "end": 4, "text": " "}, + {"type": "END_OF_LINE", "start": 4, "end": 4, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml index 1cb2a2e71d..2b365b581c 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-optional.yaml @@ -1,7 +1,7 @@ expression: \(blind\) expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "9", text: "(blind)"}, - {type: "END_OF_LINE", start: "9", end: "9", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 9, "text": "(blind)"}, + {"type": "END_OF_LINE", "start": 9, "end": 9, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml index b09505ee53..2cdac4f35a 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-parameter.yaml @@ -1,7 +1,7 @@ expression: \{string\} expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "10", text: "{string}"}, - {type: "END_OF_LINE", start: "10", end: "10", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 10, "text": "{string}"}, + {"type": "END_OF_LINE", "start": 10, "end": 10, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml index 5a972a02e0..912827a941 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-space.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-space.yaml @@ -1,7 +1,7 @@ expression: '\ ' expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "2", text: " "}, - {type: "END_OF_LINE", start: "2", end: "2", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 2, "text": " "}, + {"type": "END_OF_LINE", "start": 2, "end": 2, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml index 928efe57c9..2ddc6bb502 100644 --- a/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional-phrase.yaml @@ -1,13 +1,13 @@ expression: three (blind) mice expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "5", text: "three"}, - {type: "WHITE_SPACE", start: "5", end: "6", text: " "}, - {type: "BEGIN_OPTIONAL", start: "6", end: "7", text: "("}, - {type: "TEXT", start: "7", end: "12", text: "blind"}, - {type: "END_OPTIONAL", start: "12", end: "13", text: ")"}, - {type: "WHITE_SPACE", start: "13", end: "14", text: " "}, - {type: "TEXT", start: "14", end: "18", text: "mice"}, - {type: "END_OF_LINE", start: "18", end: "18", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_OPTIONAL", "start": 6, "end": 7, "text": "("}, + {"type": "TEXT", "start": 7, "end": 12, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 12, "end": 13, "text": ")"}, + {"type": "WHITE_SPACE", "start": 13, "end": 14, "text": " "}, + {"type": "TEXT", "start": 14, "end": 18, "text": "mice"}, + {"type": "END_OF_LINE", "start": 18, "end": 18, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/optional.yaml b/cucumber-expressions/java/testdata/tokens/optional.yaml index 8ec3f01c12..35b1474a7c 100644 --- a/cucumber-expressions/java/testdata/tokens/optional.yaml +++ b/cucumber-expressions/java/testdata/tokens/optional.yaml @@ -1,9 +1,9 @@ expression: (blind) expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "BEGIN_OPTIONAL", start: "0", end: "1", text: "("}, - {type: "TEXT", start: "1", end: "6", text: "blind"}, - {type: "END_OPTIONAL", start: "6", end: "7", text: ")"}, - {type: "END_OF_LINE", start: "7", end: "7", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_OPTIONAL", "start": 0, "end": 1, "text": "("}, + {"type": "TEXT", "start": 1, "end": 6, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 6, "end": 7, "text": ")"}, + {"type": "END_OF_LINE", "start": 7, "end": 7, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml index 2804961565..5e98055ee6 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter-phrase.yaml @@ -1,13 +1,13 @@ expression: three {string} mice expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "5", text: "three"}, - {type: "WHITE_SPACE", start: "5", end: "6", text: " "}, - {type: "BEGIN_PARAMETER", start: "6", end: "7", text: "{"}, - {type: "TEXT", start: "7", end: "13", text: "string"}, - {type: "END_PARAMETER", start: "13", end: "14", text: "}"}, - {type: "WHITE_SPACE", start: "14", end: "15", text: " "}, - {type: "TEXT", start: "15", end: "19", text: "mice"}, - {type: "END_OF_LINE", start: "19", end: "19", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_PARAMETER", "start": 6, "end": 7, "text": "{"}, + {"type": "TEXT", "start": 7, "end": 13, "text": "string"}, + {"type": "END_PARAMETER", "start": 13, "end": 14, "text": "}"}, + {"type": "WHITE_SPACE", "start": 14, "end": 15, "text": " "}, + {"type": "TEXT", "start": 15, "end": 19, "text": "mice"}, + {"type": "END_OF_LINE", "start": 19, "end": 19, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/parameter.yaml b/cucumber-expressions/java/testdata/tokens/parameter.yaml index 2372e4a058..460363c393 100644 --- a/cucumber-expressions/java/testdata/tokens/parameter.yaml +++ b/cucumber-expressions/java/testdata/tokens/parameter.yaml @@ -1,9 +1,9 @@ expression: "{string}" expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "BEGIN_PARAMETER", start: "0", end: "1", text: "{"}, - {type: "TEXT", start: "1", end: "7", text: "string"}, - {type: "END_PARAMETER", start: "7", end: "8", text: "}"}, - {type: "END_OF_LINE", start: "8", end: "8", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_PARAMETER", "start": 0, "end": 1, "text": "{"}, + {"type": "TEXT", "start": 1, "end": 7, "text": "string"}, + {"type": "END_PARAMETER", "start": 7, "end": 8, "text": "}"}, + {"type": "END_OF_LINE", "start": 8, "end": 8, "text": ""} ] diff --git a/cucumber-expressions/java/testdata/tokens/phrase.yaml b/cucumber-expressions/java/testdata/tokens/phrase.yaml index 37dd34194f..e2cfccf7b4 100644 --- a/cucumber-expressions/java/testdata/tokens/phrase.yaml +++ b/cucumber-expressions/java/testdata/tokens/phrase.yaml @@ -1,11 +1,11 @@ expression: three blind mice expected: |- [ - {type: "START_OF_LINE", start: "0", end: "0", text: ""}, - {type: "TEXT", start: "0", end: "5", text: "three"}, - {type: "WHITE_SPACE", start: "5", end: "6", text: " "}, - {type: "TEXT", start: "6", end: "11", text: "blind"}, - {type: "WHITE_SPACE", start: "11", end: "12", text: " "}, - {type: "TEXT", start: "12", end: "16", text: "mice"}, - {type: "END_OF_LINE", start: "16", end: "16", text: ""} + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "WHITE_SPACE", "start": 11, "end": 12, "text": " "}, + {"type": "TEXT", "start": 12, "end": 16, "text": "mice"}, + {"type": "END_OF_LINE", "start": 16, "end": 16, "text": ""} ] From f89858b7656fa4ff48946797f9a4b5f040f2fc57 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 11 Sep 2020 21:29:05 +0200 Subject: [PATCH 112/183] Read yaml and json in golang --- cucumber-expressions/go/ast.go | 21 +++++++++---------- .../go/cucumber_expression_tokenizer_test.go | 5 +++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index 7ce186a639..01ddd4a6f0 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -37,19 +37,18 @@ func (node node) text() string { return builder.String() } -type tokenType int +type tokenType string const ( - startOfLine tokenType = iota - endOfLine - whiteSpace - beginOptional - endOptional - beginParameter - endParameter - alternation - escape - text + startOfLine tokenType = "START_OF_LINE" + endOfLine = "END_OF_LINE" + whiteSpace = "WHITE_SPACE" + beginOptional = "BEGIN_OPTIONAL" + endOptional = "END_OPTIONAL" + beginParameter = "BEGIN_PARAMETER" + endParameter = "END_PARAMETER" + alternation = "ALTERNATION" + text = "TEXT" ) type token struct { diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index 1039d4bb49..4aa2363510 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -30,11 +30,12 @@ func TestCucumberExpressionTokenizer(t *testing.T) { // }) //}) - files, err := ioutil.ReadDir("./testdata/tokens") + directory := "../java/testdata/tokens/" + files, err := ioutil.ReadDir(directory) require.NoError(t, err) for _, file := range files { - contents, err := ioutil.ReadFile("./testdata/tokens/" + file.Name()) + contents, err := ioutil.ReadFile(directory + file.Name()) require.NoError(t, err) t.Run(fmt.Sprintf("%s", file.Name()), func(t *testing.T) { var expectation expectation From fb6acf0deb34211de438e75c75d6d4d5ef032f7b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 16 Sep 2020 20:37:46 +0200 Subject: [PATCH 113/183] Make test work --- .../go/cucumber_expression_tokenizer_test.go | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index 4aa2363510..9921095e6a 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -17,18 +17,17 @@ type expectation struct { func TestCucumberExpressionTokenizer(t *testing.T) { - //var assertContains = func(t *testing.T, expression string, expected []token) { - // tokens, err := tokenize(expression) - // require.NoError(t, err) - // require.Equal(t, expected, tokens) - //} - - //t.Run("empty string", func(t *testing.T) { - // assertContains(t, "", []token{ - // {"", startOfLine, 0, 0}, - // {"", endOfLine, 0, 0}, - // }) - //}) + var assertContains = func(t *testing.T, expression string, expected []token) { + tokens, err := tokenize(expression) + require.NoError(t, err) + require.Equal(t, expected, tokens) + } + + var assertThrows = func(t *testing.T, expression string, expected string) { + _, err := tokenize(expression) + require.Error(t, err) + require.Equal(t, expected, err.Error()) + } directory := "../java/testdata/tokens/" files, err := ioutil.ReadDir(directory) @@ -41,10 +40,15 @@ func TestCucumberExpressionTokenizer(t *testing.T) { var expectation expectation err = yaml.Unmarshal(contents, &expectation) require.NoError(t, err) - var token []token - err = json.Unmarshal([]byte(expectation.Expected), &token) - require.NoError(t, err) + if expectation.Exception == "" { + var token []token + err = json.Unmarshal([]byte(expectation.Expected), &token) + require.NoError(t, err) + assertContains(t, expectation.Expression, token) + } else { + assertThrows(t, expectation.Expression, expectation.Exception) + } }) } } From 998fa0df8814520108b24f1aad6e0dd67fe85d16 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 16 Sep 2020 20:40:46 +0200 Subject: [PATCH 114/183] empty string passes --- cucumber-expressions/go/cucumber_expression_tokenizer.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 65973eec60..d566aca3be 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -4,6 +4,11 @@ type tokenizer func(expression string, current int) (int, token) func tokenize(expression string) ([]token, error) { tokens := make([]token, 0) + index := 0 + + tokens = append(tokens, token{"", startOfLine, 0, 0}) + + tokens = append(tokens, token{"", endOfLine, index, index}) return tokens, nil } From d508aace61f97f372dd74be45d423623342e146d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 16 Sep 2020 21:32:09 +0200 Subject: [PATCH 115/183] some tests pass --- cucumber-expressions/go/ast.go | 20 ++-- .../go/cucumber_expression_tokenizer.go | 93 ++++++++++++++++++- .../CucumberExpressionTokenizer.java | 18 ++-- 3 files changed, 111 insertions(+), 20 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index 01ddd4a6f0..fe0b506f24 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -41,14 +41,14 @@ type tokenType string const ( startOfLine tokenType = "START_OF_LINE" - endOfLine = "END_OF_LINE" - whiteSpace = "WHITE_SPACE" - beginOptional = "BEGIN_OPTIONAL" - endOptional = "END_OPTIONAL" - beginParameter = "BEGIN_PARAMETER" - endParameter = "END_PARAMETER" - alternation = "ALTERNATION" - text = "TEXT" + endOfLine tokenType = "END_OF_LINE" + whiteSpace tokenType = "WHITE_SPACE" + beginOptional tokenType = "BEGIN_OPTIONAL" + endOptional tokenType = "END_OPTIONAL" + beginParameter tokenType = "BEGIN_PARAMETER" + endParameter tokenType = "END_PARAMETER" + alternation tokenType = "ALTERNATION" + text tokenType = "TEXT" ) type token struct { @@ -59,3 +59,7 @@ type token struct { } var nullNode = node{textNode, -1, -1, "", []node{}} + +func isEscapeCharacter(r rune) bool { + return false +} diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index d566aca3be..adcda1392c 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -1,14 +1,101 @@ package cucumberexpressions -type tokenizer func(expression string, current int) (int, token) +import ( + "errors" + "unicode" +) func tokenize(expression string) ([]token, error) { + tokens := make([]token, 0) - index := 0 + + runes := []rune(expression) + + var buffer []rune + previousTokenType := startOfLine + treatAsText := false + escaped := 0 + + convertBufferToToken := func(tokenType tokenType, index int) token { + escapeTokens := 0 + if tokenType == text { + escapeTokens = escaped + escaped = 0 + } + startIndex := index - len(buffer) - escapeTokens + t := token{string(buffer), tokenType, startIndex, index} + buffer = []rune{} + return t + } tokens = append(tokens, token{"", startOfLine, 0, 0}) - tokens = append(tokens, token{"", endOfLine, index, index}) + for index, r := range runes { + if !treatAsText && isEscapeCharacter(r) { + escaped++ + treatAsText = true + continue + } + + currentTokenType, err := tokenTypeOf(r, treatAsText) + if err != nil { + return nil, err + } + treatAsText = false + + if previousTokenType != startOfLine && (currentTokenType != previousTokenType || (currentTokenType != whiteSpace && currentTokenType != text)) { + token := convertBufferToToken(previousTokenType, index) + previousTokenType = currentTokenType + buffer = append(buffer, r) + tokens = append(tokens, token) + } else { + previousTokenType = currentTokenType + buffer = append(buffer, r) + } + + } + + if len(buffer) > 0 { + token := convertBufferToToken(previousTokenType, len(runes)) + tokens = append(tokens, token) + } + if treatAsText { + return nil, errors.New("can't escape EOL") + } + token := token{"", endOfLine, len(runes), len(runes)} + tokens = append(tokens, token) return tokens, nil } + +func tokenTypeOf(r rune, treatAsText bool) (tokenType, error) { + if !treatAsText { + return typeOf(r) + } + if canEscape(r) { + return text, nil + } + return startOfLine, errors.New("can't escape") + +} + +func canEscape(r rune) bool { + return false +} + +func typeOf(r rune) (tokenType, error) { + if unicode.Is(unicode.White_Space, r) { + return whiteSpace, nil + } + switch r { + case alternationCharacter: + return alternation, nil + case beginParameterCharacter: + return beginParameter, nil + case beginOptionalCharacter: + return beginOptional, nil + case endOptionalCharacter: + return endOptional, nil + } + return text, nil +} diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index e8d89380cc..e343d05269 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -30,7 +30,7 @@ private static class TokenIterator implements Iterator { private final OfInt codePoints; private StringBuilder buffer = new StringBuilder(); - private Type previousTokenType; + private Type previousTokenType = null; private Type currentTokenType = Type.START_OF_LINE; private boolean treatAsText; private int index; @@ -58,13 +58,13 @@ public Token next() { } while (codePoints.hasNext()) { - int token = codePoints.nextInt(); - if (!treatAsText && Token.isEscapeCharacter(token)) { + int codePoint = codePoints.nextInt(); + if (!treatAsText && Token.isEscapeCharacter(codePoint)) { escaped++; treatAsText = true; continue; } - currentTokenType = tokenTypeOf(token, treatAsText); + currentTokenType = tokenTypeOf(codePoint, treatAsText); treatAsText = false; if (previousTokenType != Type.START_OF_LINE @@ -72,11 +72,11 @@ public Token next() { || (currentTokenType != Type.WHITE_SPACE && currentTokenType != Type.TEXT))) { Token t = convertBufferToToken(previousTokenType); advanceTokenTypes(); - buffer.appendCodePoint(token); + buffer.appendCodePoint(codePoint); return t; } else { advanceTokenTypes(); - buffer.appendCodePoint(token); + buffer.appendCodePoint(codePoint); } } @@ -100,14 +100,14 @@ private void advanceTokenTypes() { currentTokenType = null; } - private Token convertBufferToToken(Type currentTokenType) { + private Token convertBufferToToken(Type tokenType) { int escapeTokens = 0; - if (currentTokenType == Type.TEXT) { + if (tokenType == Type.TEXT) { escapeTokens = escaped; escaped = 0; } int endIndex = index + buffer.codePointCount(0, buffer.length()) + escapeTokens; - Token t = new Token(buffer.toString(), currentTokenType, index, endIndex); + Token t = new Token(buffer.toString(), tokenType, index, endIndex); buffer = new StringBuilder(); this.index = endIndex; return t; From 57c216e8eca1e36f50a3f9083bb2ffdf14b33441 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 16 Sep 2020 21:45:37 +0200 Subject: [PATCH 116/183] more tests pass --- cucumber-expressions/go/ast.go | 57 ++++++++++++++++--- .../go/cucumber_expression_tokenizer.go | 22 ------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index fe0b506f24..f6977eb487 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -1,13 +1,16 @@ package cucumberexpressions -import "strings" +import ( + "strings" + "unicode" +) -const escapeCharacter = '\\' -const alternationCharacter = '/' -const beginParameterCharacter = '{' -const endParameterCharacter = '}' -const beginOptionalCharacter = '(' -const endOptionalCharacter = ')' +const escapeCharacter rune = '\\' +const alternationCharacter rune = '/' +const beginParameterCharacter rune = '{' +const endParameterCharacter rune = '}' +const beginOptionalCharacter rune = '(' +const endOptionalCharacter rune = ')' type nodeType int @@ -61,5 +64,45 @@ type token struct { var nullNode = node{textNode, -1, -1, "", []node{}} func isEscapeCharacter(r rune) bool { + return r == escapeCharacter +} + +func canEscape(r rune) bool { + if unicode.Is(unicode.White_Space, r) { + return true + } + switch r { + case escapeCharacter: + return true + case alternationCharacter: + return true + case beginParameterCharacter: + return true + case endParameterCharacter: + return true + case beginOptionalCharacter: + return true + case endOptionalCharacter: + return true + } return false } + +func typeOf(r rune) (tokenType, error) { + if unicode.Is(unicode.White_Space, r) { + return whiteSpace, nil + } + switch r { + case alternationCharacter: + return alternation, nil + case beginParameterCharacter: + return beginParameter, nil + case endParameterCharacter: + return endParameter, nil + case beginOptionalCharacter: + return beginOptional, nil + case endOptionalCharacter: + return endOptional, nil + } + return text, nil +} diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index adcda1392c..b234e9cb27 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -2,7 +2,6 @@ package cucumberexpressions import ( "errors" - "unicode" ) func tokenize(expression string) ([]token, error) { @@ -78,24 +77,3 @@ func tokenTypeOf(r rune, treatAsText bool) (tokenType, error) { return startOfLine, errors.New("can't escape") } - -func canEscape(r rune) bool { - return false -} - -func typeOf(r rune) (tokenType, error) { - if unicode.Is(unicode.White_Space, r) { - return whiteSpace, nil - } - switch r { - case alternationCharacter: - return alternation, nil - case beginParameterCharacter: - return beginParameter, nil - case beginOptionalCharacter: - return beginOptional, nil - case endOptionalCharacter: - return endOptional, nil - } - return text, nil -} From 4f65ab169a01e99be5c7f62aac6b215ebe965c73 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 11:26:41 +0200 Subject: [PATCH 117/183] More tests pass --- cucumber-expressions/go/ast.go | 35 +- .../go/cucumber_expression.go | 24 +- .../go/cucumber_expression_parser.go | 20 +- .../go/cucumber_expression_parser_test.go | 345 ++---------------- .../go/cucumber_expression_tokenizer.go | 14 +- .../io/cucumber/cucumberexpressions/Ast.java | 13 +- .../CucumberExpressionTokenizer.java | 22 +- .../testdata/ast/anonymous-parameter.yaml | 2 +- .../java/testdata/ast/empty-alternation.yaml | 4 +- .../java/testdata/ast/empty-alternations.yaml | 6 +- .../java/testdata/ast/empty-string.yaml | 2 +- .../java/testdata/ast/escaped-backslash.yaml | 2 +- 12 files changed, 106 insertions(+), 383 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index f6977eb487..1e5b01e493 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -12,29 +12,34 @@ const endParameterCharacter rune = '}' const beginOptionalCharacter rune = '(' const endOptionalCharacter rune = ')' -type nodeType int +type nodeType string const ( - textNode nodeType = iota - optionalNode - alternationNode - alternativeNode - parameterNode - expressionNode + textNode nodeType = "TEXT_NODE" + optionalNode nodeType = "OPTIONAL_NODE" + alternationNode nodeType = "ALTERNATION_NODE" + alternativeNode nodeType = "ALTERNATIVE_NODE" + parameterNode nodeType = "PARAMETER_NODE" + expressionNode nodeType = "EXPRESSION_NODE" ) type node struct { - nodeType nodeType - start int - end int - token string - nodes []node + NodeType nodeType `json:"type"` + Start int `json:"start"` + End int `json:"end"` + Token string `json:"token"` + Nodes []node `json:"nodes"` } func (node node) text() string { builder := strings.Builder{} - builder.WriteString(node.token) - for _, c := range node.nodes { + builder.WriteString(node.Token) + + if node.Nodes == nil { + return builder.String() + } + + for _, c := range node.Nodes { builder.WriteString(c.text()) } return builder.String() @@ -61,7 +66,7 @@ type token struct { End int `json:"end"` } -var nullNode = node{textNode, -1, -1, "", []node{}} +var nullNode = node{textNode, -1, -1, "", nil} func isEscapeCharacter(r rune) bool { return r == escapeCharacter diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 7c6e567f17..376fd05312 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -40,9 +40,9 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy } func (c *CucumberExpression) rewriteNodeToRegex(node node) (string, error) { - switch node.nodeType { + switch node.NodeType { case textNode: - return c.processEscapes(node.token), nil + return c.processEscapes(node.Token), nil case optionalNode: return c.rewriteOptional(node) case alternationNode: @@ -71,13 +71,13 @@ func (c *CucumberExpression) rewriteOptional(node node) (string, error) { if err != nil { return "", err } - return c.rewriteNodesToRegex(node.nodes, "", "(?:", ")?") + return c.rewriteNodesToRegex(node.Nodes, "", "(?:", ")?") } func (c *CucumberExpression) rewriteAlternation(node node) (string, error) { // Make sure the alternative parts aren't empty and don't contain parameter types - for _, alternative := range node.nodes { - if len(alternative.nodes) == 0 { + for _, alternative := range node.Nodes { + if len(alternative.Nodes) == 0 { return "", NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) } err := c.assertNoParameters(alternative, parameterTypesCanNotBeAlternative) @@ -89,11 +89,11 @@ func (c *CucumberExpression) rewriteAlternation(node node) (string, error) { return "", err } } - return c.rewriteNodesToRegex(node.nodes, "|", "(?:", ")") + return c.rewriteNodesToRegex(node.Nodes, "|", "(?:", ")") } func (c *CucumberExpression) rewriteAlternative(node node) (string, error) { - return c.rewriteNodesToRegex(node.nodes, "", "", "") + return c.rewriteNodesToRegex(node.Nodes, "", "", "") } func (c *CucumberExpression) rewriteParameter(node node) (string, error) { @@ -125,7 +125,7 @@ func (c *CucumberExpression) rewriteParameter(node node) (string, error) { } func (c *CucumberExpression) rewriteExpression(node node) (string, error) { - return c.rewriteNodesToRegex(node.nodes, "", "^", "$") + return c.rewriteNodesToRegex(node.Nodes, "", "^", "$") } func (c *CucumberExpression) rewriteNodesToRegex(nodes []node, delimiter string, prefix string, suffix string) (string, error) { @@ -146,8 +146,8 @@ func (c *CucumberExpression) rewriteNodesToRegex(nodes []node, delimiter string, } func (c *CucumberExpression) assertNotEmpty(node node, message string) error { - for _, node := range node.nodes { - if node.nodeType == textNode { + for _, node := range node.Nodes { + if node.NodeType == textNode { return nil } } @@ -155,8 +155,8 @@ func (c *CucumberExpression) assertNotEmpty(node node, message string) error { } func (c *CucumberExpression) assertNoParameters(node node, message string) error { - for _, node := range node.nodes { - if node.nodeType == parameterNode { + for _, node := range node.Nodes { + if node.NodeType == parameterNode { return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) } } diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index e5c30b0900..54d6a5999c 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -5,7 +5,7 @@ package cucumberexpressions */ var textParser = func(tokens []token, current int) (int, node) { token := tokens[current] - return 1, node{textNode, token.Start, token.End, token.Text, []node{}} + return 1, node{textNode, token.Start, token.End, token.Text, nil} } /* @@ -36,7 +36,7 @@ var alternativeSeparatorParser = func(tokens []token, current int) (int, node) { return 0, nullNode } token := tokens[current] - return 1, node{alternativeNode, token.Start, token.End, token.Text, []node{}} + return 1, node{alternativeNode, token.Start, token.End, token.Text, nil} } var alternativeParsers = []parser{ @@ -60,7 +60,7 @@ var alternationParser = func(tokens []token, current int) (int, node) { consumed, subAst := parseTokensUntil(alternativeParsers, tokens, current, whiteSpace, endOfLine) var contains = func(s []node, nodeType nodeType) bool { for _, a := range s { - if a.nodeType == nodeType { + if a.NodeType == nodeType { return true } } @@ -73,7 +73,7 @@ var alternationParser = func(tokens []token, current int) (int, node) { // Does not consume right hand boundary token start := tokens[current].Start - end := tokens[subCurrent].End + end := tokens[subCurrent].Start return consumed, node{alternationNode, start, end, "", splitAlternatives(start, end, subAst)} } @@ -183,7 +183,7 @@ func splitAlternatives(start int, end int, alternation []node) []node { alternatives := make([][]node, 0) alternative := make([]node, 0) for _, n := range alternation { - if n.nodeType == alternativeNode { + if n.NodeType == alternativeNode { separators = append(separators, n) alternatives = append(alternatives, alternative) alternative = make([]node, 0) @@ -201,14 +201,14 @@ func createAlternativeNodes(start int, end int, separators []node, alternatives for i, n := range alternatives { if i == 0 { rightSeparator := separators[i] - nodes = append(nodes, node{alternativeNode, start, rightSeparator.start, "", n}) + nodes = append(nodes, node{alternativeNode, start, rightSeparator.Start, "", n}) } else if i == len(alternatives)-1 { leftSeparator := separators[i-1] - nodes = append(nodes, node{alternativeNode, leftSeparator.end, end, "", n}) + nodes = append(nodes, node{alternativeNode, leftSeparator.End, end, "", n}) } else { - rightSeparator := separators[i-1] - leftSeparator := separators[i] - nodes = append(nodes, node{alternativeNode, leftSeparator.end, rightSeparator.start, "", n}) + leftSeparator := separators[i-1] + rightSeparator := separators[i] + nodes = append(nodes, node{alternativeNode, leftSeparator.End, rightSeparator.Start, "", n}) } } return nodes diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index 3b44b0f1f3..97939951c5 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -1,7 +1,11 @@ package cucumberexpressions import ( + "encoding/json" + "fmt" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + "io/ioutil" "testing" ) @@ -10,324 +14,35 @@ func TestCucumberExpressionParser(t *testing.T) { ast, err := parse(expression) require.NoError(t, err) require.Equal(t, expected, ast) + require.Equal(t, expected, ast) + } + var assertThrows = func(t *testing.T, expression string, expected string) { + _, err := parse(expression) + require.Error(t, err) + require.Equal(t, expected, err.Error()) } - t.Run("empty string", func(t *testing.T) { - assertAst(t, "", node{ - expressionNode, -1, -1, "", - []node{}, - }) - }) - - t.Run("phrase", func(t *testing.T) { - assertAst(t, "three blind mice", node{ - expressionNode, -1, -1, "", - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "blind", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - }, - }) - }) - - t.Run("optional", func(t *testing.T) { - assertAst(t, "(blind)", node{ - expressionNode, -1, -1, "", - []node{ - {optionalNode, - -1, - -1, - "", - []node{ - {textNode, -1, -1, "blind", []node{}}, - }, - }, - }, - }) - }) - - t.Run("parameter", func(t *testing.T) { - assertAst(t, "{string}", node{ - expressionNode, -1, -1, "", - []node{ - {parameterNode, -1, -1, - "", - []node{ - {textNode, -1, -1, "string", []node{}}, - }, - }, - }, - }) - }) - - t.Run("anonymous parameter", func(t *testing.T) { - assertAst(t, "{}", node{ - expressionNode, -1, -1, "", - []node{ - {parameterNode, -1, -1, "", []node{}}, - }, - }) - }) - - t.Run("optional phrase", func(t *testing.T) { - assertAst(t, "three (blind) mice", node{ - expressionNode, -1, -1, "", - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {optionalNode, -1, -1, "", []node{ - {textNode, -1, -1, "blind", []node{}}, - }}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - }, - }) - }) - - t.Run("slash", func(t *testing.T) { - assertAst(t, "\\", node{ - expressionNode, -1, -1, "", - []node{ - {textNode, -1, -1, "\\", []node{}}, - }, - }) - }) - - t.Run("opening brace", func(t *testing.T) { - assertAst(t, "{", node{ - expressionNode, -1, -1, "", - []node{ - {textNode, -1, -1, "{", []node{}}, - }, - }) - }) - - t.Run("unfinished parameter", func(t *testing.T) { - assertAst(t, "{string", node{ - expressionNode, -1, -1, "", - []node{ - {textNode, -1, -1, "{", []node{}}, - {textNode, -1, -1, "string", []node{}}, - }, - }) - }) - - t.Run("opening parenthesis", func(t *testing.T) { - assertAst(t, "(", node{ - expressionNode, -1, -1, "", - []node{ - {textNode, -1, -1, "(", []node{}}, - }, - }) - }) - - t.Run("escaped opening parenthesis", func(t *testing.T) { - assertAst(t, "\\(", node{ - expressionNode, -1, -1, "", - []node{ - {textNode, -1, -1, "(", []node{}}, - }, - }) - }) - - t.Run("escaped optional phrase", func(t *testing.T) { - assertAst(t, "three \\(blind) mice", node{ - expressionNode, -1, -1, "", - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "(", []node{}}, - {textNode, -1, -1, "blind", []node{}}, - {textNode, -1, -1, ")", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - }, - }) - }) - - t.Run("escaped optional followed by optional", func(t *testing.T) { - assertAst(t, "three \\((very) blind) mice", node{ - expressionNode, -1, -1, "", - - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "(", []node{}}, - {optionalNode, -1, -1, "", []node{ - {textNode, -1, -1, "very", []node{}}, - }}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "blind", []node{}}, - {textNode, -1, -1, ")", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - }, - }) - }) - - t.Run("optional containing escaped optional", func(t *testing.T) { - assertAst(t, "three ((very\\) blind) mice", node{ - expressionNode, -1, -1, "", - - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {optionalNode, -1, -1, "", []node{ - {textNode, -1, -1, "(", []node{}}, - {textNode, -1, -1, "very", []node{}}, - {textNode, -1, -1, ")", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "blind", []node{}}, - }}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - }, - }) - }) - - t.Run("alternation", func(t *testing.T) { - assertAst(t, "mice/rats", node{ - expressionNode, -1, -1, "", - - []node{ - {alternationNode, -1, -1, "", - []node{ - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "mice", []node{}}, - }}, - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "rats", []node{}}, - }}, - }, - }, - }, - }) - }) - - t.Run("escaped alternation", func(t *testing.T) { - assertAst(t, "mice\\/rats", node{ - expressionNode, -1, -1, "", - - []node{ - {textNode, -1, -1, "mice", []node{}}, - {textNode, -1, -1, "/", []node{}}, - {textNode, -1, -1, "rats", []node{}}, - }, - }) - }) - - t.Run("alternation phrase", func(t *testing.T) { - assertAst(t, "three hungry/blind mice", node{ - expressionNode, -1, -1, "", - - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {alternationNode, -1, -1, "", []node{ - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "hungry", []node{}}, - }}, - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "blind", []node{}}, - }}, - }}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - }, - }) - }) - - t.Run("alternation with whitespace", func(t *testing.T) { - assertAst(t, "\\ three\\ hungry/blind\\ mice\\ ", node{ - expressionNode, -1, -1, "", - - []node{ - {alternationNode, -1, -1, "", []node{ - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "hungry", []node{}}, - }}, - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "blind", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - {textNode, -1, -1, " ", []node{}}, - }}, - }}, - }, - }) - }) - - t.Run("alternation with unused end optional", func(t *testing.T) { - assertAst(t, "three )blind\\ mice/rats", node{ - expressionNode, -1, -1, "", - - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {alternationNode, -1, -1, "", []node{ - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, ")", []node{}}, - {textNode, -1, -1, "blind", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - }}, - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "rats", []node{}}, - }}, - }}, - }, - }) - }) - - t.Run("alternation with unused start optional", func(t *testing.T) { - assertAst(t, "three blind\\ mice/rats(", node{ - expressionNode, -1, -1, "", - - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {alternationNode, -1, -1, "", []node{ - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "blind", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "mice", []node{}}, - }}, - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "rats", []node{}}, - {textNode, -1, -1, "(", []node{}}, - }}, - }}, - }, - }) - }) - - t.Run("alternation with unused start optional", func(t *testing.T) { - assertAst(t, "three blind\\ rat/cat(s)", node{ - expressionNode, -1, -1, "", + directory := "../java/testdata/ast/" + files, err := ioutil.ReadDir(directory) + require.NoError(t, err) - []node{ - {textNode, -1, -1, "three", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {alternationNode, -1, -1, "", []node{ - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "blind", []node{}}, - {textNode, -1, -1, " ", []node{}}, - {textNode, -1, -1, "rat", []node{}}, - }}, - {alternativeNode, -1, -1, "", []node{ - {textNode, -1, -1, "cat", []node{}}, - {optionalNode, -1, -1, "", []node{ - {textNode, -1, -1, "s", []node{}}, - }}, - }}, - }}, - }, + for _, file := range files { + contents, err := ioutil.ReadFile(directory + file.Name()) + require.NoError(t, err) + t.Run(fmt.Sprintf("%s", file.Name()), func(t *testing.T) { + var expectation expectation + err = yaml.Unmarshal(contents, &expectation) + require.NoError(t, err) + + if expectation.Exception == "" { + var node node + err = json.Unmarshal([]byte(expectation.Expected), &node) + require.NoError(t, err) + assertAst(t, expectation.Expression, node) + } else { + assertThrows(t, expectation.Expression, expectation.Exception) + } }) - }) + } } diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index b234e9cb27..7117009714 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -14,22 +14,24 @@ func tokenize(expression string) ([]token, error) { previousTokenType := startOfLine treatAsText := false escaped := 0 + bufferStartIndex := 0 - convertBufferToToken := func(tokenType tokenType, index int) token { + convertBufferToToken := func(tokenType tokenType) token { escapeTokens := 0 if tokenType == text { escapeTokens = escaped escaped = 0 } - startIndex := index - len(buffer) - escapeTokens - t := token{string(buffer), tokenType, startIndex, index} + consumedIndex := bufferStartIndex + len(buffer) + escapeTokens + t := token{string(buffer), tokenType, bufferStartIndex, consumedIndex} buffer = []rune{} + bufferStartIndex = consumedIndex return t } tokens = append(tokens, token{"", startOfLine, 0, 0}) - for index, r := range runes { + for _, r := range runes { if !treatAsText && isEscapeCharacter(r) { escaped++ treatAsText = true @@ -43,7 +45,7 @@ func tokenize(expression string) ([]token, error) { treatAsText = false if previousTokenType != startOfLine && (currentTokenType != previousTokenType || (currentTokenType != whiteSpace && currentTokenType != text)) { - token := convertBufferToToken(previousTokenType, index) + token := convertBufferToToken(previousTokenType) previousTokenType = currentTokenType buffer = append(buffer, r) tokens = append(tokens, token) @@ -55,7 +57,7 @@ func tokenize(expression string) ([]token, error) { } if len(buffer) > 0 { - token := convertBufferToToken(previousTokenType, len(runes)) + token := convertBufferToToken(previousTokenType) tokens = append(tokens, token) } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java index 8fa4f4c0af..ba5eb4d5ee 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java @@ -36,10 +36,6 @@ static final class Node implements Located { this(type, start, end, null, token); } - Node(Type type, int start, int end, Node... nodes) { - this(type, start, end, asList(nodes)); - } - Node(Type type, int start, int end, List nodes) { this(type, start, end, nodes, null); } @@ -105,11 +101,12 @@ private StringBuilder toString(int depth) { .append(end); if (token != null) { - sb.append(", \"token\": \"").append(token).append("\""); + sb.append(", \"token\": \"").append(token.replaceAll("\\\\", "\\\\\\\\")).append("\""); } - if (nodes != null && !nodes.isEmpty()) { + if (nodes != null) { sb.append(", \"nodes\": "); + if (!nodes.isEmpty()) { StringBuilder padding = new StringBuilder(); for (int i = 0; i < depth; i++) { padding.append(" "); @@ -117,6 +114,10 @@ private StringBuilder toString(int depth) { sb.append(nodes.stream() .map(node -> node.toString(depth + 1)) .collect(joining(",\n", "[\n", "\n" +padding + "]"))); + + } else { + sb.append("[]"); + } } sb.append("}"); return sb; diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index e343d05269..cea6c5113e 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -33,7 +33,7 @@ private static class TokenIterator implements Iterator { private Type previousTokenType = null; private Type currentTokenType = Type.START_OF_LINE; private boolean treatAsText; - private int index; + private int bufferStartIndex; private int escaped; TokenIterator(String expression) { @@ -67,16 +67,15 @@ public Token next() { currentTokenType = tokenTypeOf(codePoint, treatAsText); treatAsText = false; - if (previousTokenType != Type.START_OF_LINE - && (currentTokenType != previousTokenType - || (currentTokenType != Type.WHITE_SPACE && currentTokenType != Type.TEXT))) { + if (previousTokenType == Type.START_OF_LINE || (currentTokenType == previousTokenType + && (currentTokenType == Type.WHITE_SPACE || currentTokenType == Type.TEXT))) { + advanceTokenTypes(); + buffer.appendCodePoint(codePoint); + } else { Token t = convertBufferToToken(previousTokenType); advanceTokenTypes(); buffer.appendCodePoint(codePoint); return t; - } else { - advanceTokenTypes(); - buffer.appendCodePoint(codePoint); } } @@ -106,10 +105,10 @@ private Token convertBufferToToken(Type tokenType) { escapeTokens = escaped; escaped = 0; } - int endIndex = index + buffer.codePointCount(0, buffer.length()) + escapeTokens; - Token t = new Token(buffer.toString(), tokenType, index, endIndex); + int consumedIndex = bufferStartIndex + buffer.codePointCount(0, buffer.length()) + escapeTokens; + Token t = new Token(buffer.toString(), tokenType, bufferStartIndex, consumedIndex); buffer = new StringBuilder(); - this.index = endIndex; + this.bufferStartIndex = consumedIndex; return t; } @@ -120,7 +119,8 @@ private Type tokenTypeOf(Integer token, boolean treatAsText) { if (Token.canEscape(token)) { return Type.TEXT; } - throw createCantEscape(expression, index + escaped); + // Buffer always start at + throw createCantEscape(expression, bufferStartIndex + buffer.codePointCount(0, buffer.length()) + escaped); } } diff --git a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml index 940bd650f9..2c4d339333 100644 --- a/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/anonymous-parameter.yaml @@ -1,5 +1,5 @@ expression: "{}" expected: |- {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ - {"type": "PARAMETER_NODE", "start": 0, "end": 2} + {"type": "PARAMETER_NODE", "start": 0, "end": 2, "nodes": []} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml index a7f5cbfa60..6d810fc8f3 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternation.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternation.yaml @@ -2,7 +2,7 @@ expression: / expected: |- {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ {"type": "ALTERNATION_NODE", "start": 0, "end": 1, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0}, - {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1} + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml index 90978553e4..f8d4dd4cf8 100644 --- a/cucumber-expressions/java/testdata/ast/empty-alternations.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-alternations.yaml @@ -2,8 +2,8 @@ expression: '//' expected: |- {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ {"type": "ALTERNATION_NODE", "start": 0, "end": 2, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0}, - {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1}, - {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2} + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2, "nodes": []} ]} ]} diff --git a/cucumber-expressions/java/testdata/ast/empty-string.yaml b/cucumber-expressions/java/testdata/ast/empty-string.yaml index 4f1efe330c..4d33c2dc76 100644 --- a/cucumber-expressions/java/testdata/ast/empty-string.yaml +++ b/cucumber-expressions/java/testdata/ast/empty-string.yaml @@ -1,3 +1,3 @@ expression: "" expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 0} + {"type": "EXPRESSION_NODE", "start": 0, "end": 0, "nodes": []} diff --git a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml index af7b520dae..da2d008e1e 100644 --- a/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml +++ b/cucumber-expressions/java/testdata/ast/escaped-backslash.yaml @@ -1,5 +1,5 @@ expression: '\\' expected: |- {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\"} + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\\"} ]} From ca412215b7c9ad510eab1fdfc9c7f1c40aebb7b2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 11:55:44 +0200 Subject: [PATCH 118/183] Fix more --- .../go/cucumber_expression_test.go | 14 +++++++++++++- .../go/cucumber_expression_tokenizer.go | 10 +++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 773de065db..f57b091a73 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -389,7 +389,7 @@ func TestCucumberExpression(t *testing.T) { }) t.Run("escapes special characters", func(t *testing.T) { - for _, char := range []string{"\\", "[", "]", "^", "$", ".", "|", "?", "*", "+"} { + for _, char := range []string{"[", "]", "^", "$", ".", "|", "?", "*", "+"} { t.Run(fmt.Sprintf("escapes %s", char), func(t *testing.T) { require.Equal( t, @@ -401,6 +401,18 @@ func TestCucumberExpression(t *testing.T) { []interface{}{800}, ) }) + t.Run("escapes \\", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression( + t, + "I have {int} cuke(s) and \\\\", + "I have 800 cukes and \\", + ), + []interface{}{800}, + ) + }) + } t.Run("escapes .", func(t *testing.T) { diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 7117009714..311d52462d 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -31,14 +31,14 @@ func tokenize(expression string) ([]token, error) { tokens = append(tokens, token{"", startOfLine, 0, 0}) - for _, r := range runes { - if !treatAsText && isEscapeCharacter(r) { + for _, rune := range runes { + if !treatAsText && isEscapeCharacter(rune) { escaped++ treatAsText = true continue } - currentTokenType, err := tokenTypeOf(r, treatAsText) + currentTokenType, err := tokenTypeOf(rune, treatAsText) if err != nil { return nil, err } @@ -47,11 +47,11 @@ func tokenize(expression string) ([]token, error) { if previousTokenType != startOfLine && (currentTokenType != previousTokenType || (currentTokenType != whiteSpace && currentTokenType != text)) { token := convertBufferToToken(previousTokenType) previousTokenType = currentTokenType - buffer = append(buffer, r) + buffer = append(buffer, rune) tokens = append(tokens, token) } else { previousTokenType = currentTokenType - buffer = append(buffer, r) + buffer = append(buffer, rune) } } From 27949d6674d5d484af33257cbe5ec5cc127835e1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 12:08:01 +0200 Subject: [PATCH 119/183] Throw errors when expected --- .../go/cucumber_expression_parser.go | 66 ++++++++++++------- .../go/cucumber_expression_tokenizer.go | 8 +-- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 54d6a5999c..e5d8587406 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -3,9 +3,9 @@ package cucumberexpressions /* * text := token */ -var textParser = func(tokens []token, current int) (int, node) { +var textParser = func(tokens []token, current int) (int, node, error) { token := tokens[current] - return 1, node{textNode, token.Start, token.End, token.Text, nil} + return 1, node{textNode, token.Start, token.End, token.Text, nil}, nil } /* @@ -31,12 +31,12 @@ var optionalParser = parseBetween( ) // alternation := alternative* + ( '/' + alternative* )+ -var alternativeSeparatorParser = func(tokens []token, current int) (int, node) { +var alternativeSeparatorParser = func(tokens []token, current int) (int, node, error) { if !lookingAt(tokens, current, alternation) { - return 0, nullNode + return 0, nullNode, nil } token := tokens[current] - return 1, node{alternativeNode, token.Start, token.End, token.Text, nil} + return 1, node{alternativeNode, token.Start, token.End, token.Text, nil}, nil } var alternativeParsers = []parser{ @@ -51,13 +51,17 @@ var alternativeParsers = []parser{ * boundary := whitespace | ^ | $ * alternative: = optional | parameter | text */ -var alternationParser = func(tokens []token, current int) (int, node) { +var alternationParser = func(tokens []token, current int) (int, node, error) { previous := current - 1 if !lookingAtAny(tokens, previous, startOfLine, whiteSpace) { - return 0, nullNode + return 0, nullNode, nil + } + + consumed, subAst, err := parseTokensUntil(alternativeParsers, tokens, current, whiteSpace, endOfLine) + if err != nil { + return 0, nullNode, err } - consumed, subAst := parseTokensUntil(alternativeParsers, tokens, current, whiteSpace, endOfLine) var contains = func(s []node, nodeType nodeType) bool { for _, a := range s { if a.NodeType == nodeType { @@ -68,13 +72,13 @@ var alternationParser = func(tokens []token, current int) (int, node) { } subCurrent := current + consumed if !contains(subAst, alternativeNode) { - return 0, nullNode + return 0, nullNode, nil } // Does not consume right hand boundary token start := tokens[current].Start end := tokens[subCurrent].Start - return consumed, node{alternationNode, start, end, "", splitAlternatives(start, end, subAst)} + return consumed, node{alternationNode, start, end, "", splitAlternatives(start, end, subAst)}, nil } /* @@ -95,7 +99,10 @@ func parse(expression string) (node, error) { if err != nil { return nullNode, err } - consumed, ast := cucumberExpressionParser(tokens, 0) + consumed, ast, err := cucumberExpressionParser(tokens, 0) + if err != nil { + return nullNode, err + } if consumed != len(tokens) { // Can't happen if configured properly return nullNode, NewCucumberExpressionError("Could not parse" + expression) @@ -103,30 +110,33 @@ func parse(expression string) (node, error) { return ast, nil } -type parser func(tokens []token, current int) (int, node) +type parser func(tokens []token, current int) (int, node, error) func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, parsers ...parser) parser { - return func(tokens []token, current int) (int, node) { + return func(tokens []token, current int) (int, node, error) { if !lookingAt(tokens, current, beginToken) { - return 0, nullNode + return 0, nullNode, nil } subCurrent := current + 1 - consumed, subAst := parseTokensUntil(parsers, tokens, subCurrent, endToken) + consumed, subAst, err := parseTokensUntil(parsers, tokens, subCurrent, endToken) + if err != nil { + return 0, nullNode, err + } subCurrent += consumed // endToken not found if !lookingAt(tokens, subCurrent, endToken) { - return 0, nullNode + return 0, nullNode, NewCucumberExpressionError("No end token") } // consumes endToken start := tokens[current].Start end := tokens[subCurrent].End - return subCurrent + 1 - current, node{nodeType, start, end, "", subAst} + return subCurrent + 1 - current, node{nodeType, start, end, "", subAst}, nil } } -func parseTokensUntil(parsers []parser, expresion []token, startAt int, endTokens ...tokenType) (int, []node) { +func parseTokensUntil(parsers []parser, expresion []token, startAt int, endTokens ...tokenType) (int, []node, error) { ast := make([]node, 0) current := startAt size := len(expresion) @@ -134,28 +144,34 @@ func parseTokensUntil(parsers []parser, expresion []token, startAt int, endToken if lookingAtAny(expresion, current, endTokens...) { break } - consumed, node := parseToken(parsers, expresion, current) + consumed, node, err := parseToken(parsers, expresion, current) + if err != nil { + return 0, nil, nil + } if consumed == 0 { // If configured correctly this will never happen // Keep to avoid infinite loops - break + return 0, nil, NewCucumberExpressionError("No eligible parsers") } current += consumed ast = append(ast, node) } - return current - startAt, ast + return current - startAt, ast, nil } -func parseToken(parsers []parser, expresion []token, startAt int) (int, node) { +func parseToken(parsers []parser, expresion []token, startAt int) (int, node, error) { for _, parser := range parsers { - consumed, node := parser(expresion, startAt) + consumed, node, err := parser(expresion, startAt) + if err != nil { + return 0, nullNode, err + } if consumed != 0 { - return consumed, node + return consumed, node, nil } } // If configured correctly this will never happen - return 0, nullNode + return 0, nullNode, NewCucumberExpressionError("No eligible parsers") } func lookingAtAny(tokens []token, at int, tokenTypes ...tokenType) bool { diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 311d52462d..b3971aa4f6 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -1,9 +1,5 @@ package cucumberexpressions -import ( - "errors" -) - func tokenize(expression string) ([]token, error) { tokens := make([]token, 0) @@ -62,7 +58,7 @@ func tokenize(expression string) ([]token, error) { } if treatAsText { - return nil, errors.New("can't escape EOL") + return nil, NewCucumberExpressionError("can't escape EOL") } token := token{"", endOfLine, len(runes), len(runes)} tokens = append(tokens, token) @@ -76,6 +72,6 @@ func tokenTypeOf(r rune, treatAsText bool) (tokenType, error) { if canEscape(r) { return text, nil } - return startOfLine, errors.New("can't escape") + return startOfLine, NewCucumberExpressionError("can't escape") } From 881e8ce3011b2953c0c6cfae886a6c71ef1ca7aa Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 12:09:06 +0200 Subject: [PATCH 120/183] Naming --- .../go/cucumber_expression_tokenizer.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index b3971aa4f6..64567b72da 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -27,14 +27,14 @@ func tokenize(expression string) ([]token, error) { tokens = append(tokens, token{"", startOfLine, 0, 0}) - for _, rune := range runes { - if !treatAsText && isEscapeCharacter(rune) { + for _, r := range runes { + if !treatAsText && isEscapeCharacter(r) { escaped++ treatAsText = true continue } - currentTokenType, err := tokenTypeOf(rune, treatAsText) + currentTokenType, err := tokenTypeOf(r, treatAsText) if err != nil { return nil, err } @@ -43,11 +43,11 @@ func tokenize(expression string) ([]token, error) { if previousTokenType != startOfLine && (currentTokenType != previousTokenType || (currentTokenType != whiteSpace && currentTokenType != text)) { token := convertBufferToToken(previousTokenType) previousTokenType = currentTokenType - buffer = append(buffer, rune) + buffer = append(buffer, r) tokens = append(tokens, token) } else { previousTokenType = currentTokenType - buffer = append(buffer, rune) + buffer = append(buffer, r) } } From 1897cef42c06ee92c972343acb4610fcacfea479 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 12:27:24 +0200 Subject: [PATCH 121/183] Fix examples --- cucumber-expressions/examples.txt | 4 +-- .../go/cucumber_expression_parser.go | 9 ++++--- .../go/cucumber_expression_test.go | 14 ---------- cucumber-expressions/go/examples.txt | 4 +++ cucumber-expressions/java/examples.txt | 4 +++ .../CucumberExpressionParserTest.java | 11 +++++--- .../CucumberExpressionTokenizerTest.java | 11 +++++--- .../ast/alternation-with-parameter.yaml | 27 +++++++++++++++++++ ...ternation-with-unused-start-optional.yaml} | 0 9 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 cucumber-expressions/java/testdata/ast/alternation-with-parameter.yaml rename cucumber-expressions/java/testdata/ast/{alternation-with-unused_start-optional.yaml => alternation-with-unused-start-optional.yaml} (100%) diff --git a/cucumber-expressions/examples.txt b/cucumber-expressions/examples.txt index 367e84c2fa..3a8288d34d 100644 --- a/cucumber-expressions/examples.txt +++ b/cucumber-expressions/examples.txt @@ -37,7 +37,7 @@ a purchase for $33 Some ${float} of cukes at {int}° Celsius Some $3.50 of cukes at 42° Celsius [3.5,42] ----- -I select the {int}st/nd/rd/th +--- +I select the {int}st/nd/rd/th Cucumber I select the 3rd Cucumber [3] diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index e5d8587406..94f139d291 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -47,17 +47,18 @@ var alternativeParsers = []parser{ } /* - * alternation := (?<=boundary) + alternative* + ( '/' + alternative* )+ + (?=boundary) - * boundary := whitespace | ^ | $ + * alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary) + * left-boundary := whitespace | } | ^ + * right-boundary := whitespace | { | $ * alternative: = optional | parameter | text */ var alternationParser = func(tokens []token, current int) (int, node, error) { previous := current - 1 - if !lookingAtAny(tokens, previous, startOfLine, whiteSpace) { + if !lookingAtAny(tokens, previous, startOfLine, whiteSpace, endParameter) { return 0, nullNode, nil } - consumed, subAst, err := parseTokensUntil(alternativeParsers, tokens, current, whiteSpace, endOfLine) + consumed, subAst, err := parseTokensUntil(alternativeParsers, tokens, current, whiteSpace, endOfLine, beginParameter) if err != nil { return 0, nullNode, err } diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index f57b091a73..dd9c9a9c38 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -360,20 +360,6 @@ func TestCucumberExpression(t *testing.T) { ) }) - t.Run("does not allow text/parameter type alternation", func(t *testing.T) { - parameterTypeRegistry := NewParameterTypeRegistry() - _, err := NewCucumberExpression("x/{int}", parameterTypeRegistry) - require.Error(t, err) - require.Equal(t, "Parameter types cannot be alternative: x/{int}", err.Error()) - }) - - t.Run("does not allow parameter type/text alternation", func(t *testing.T) { - parameterTypeRegistry := NewParameterTypeRegistry() - _, err := NewCucumberExpression("{int}/x", parameterTypeRegistry) - require.Error(t, err) - require.Equal(t, "Parameter types cannot be alternative: {int}/x", err.Error()) - }) - t.Run("returns UndefinedParameterTypeExpression for unknown parameter", func(t *testing.T) { parameterTypeRegistry := NewParameterTypeRegistry() _, err := NewCucumberExpression("{unknown}", parameterTypeRegistry) diff --git a/cucumber-expressions/go/examples.txt b/cucumber-expressions/go/examples.txt index c6acb6958e..3a8288d34d 100644 --- a/cucumber-expressions/go/examples.txt +++ b/cucumber-expressions/go/examples.txt @@ -37,3 +37,7 @@ a purchase for $33 Some ${float} of cukes at {int}° Celsius Some $3.50 of cukes at 42° Celsius [3.5,42] +--- +I select the {int}st/nd/rd/th Cucumber +I select the 3rd Cucumber +[3] diff --git a/cucumber-expressions/java/examples.txt b/cucumber-expressions/java/examples.txt index c6acb6958e..3a8288d34d 100644 --- a/cucumber-expressions/java/examples.txt +++ b/cucumber-expressions/java/examples.txt @@ -37,3 +37,7 @@ a purchase for $33 Some ${float} of cukes at {int}° Celsius Some $3.50 of cukes at 42° Celsius [3.5,42] +--- +I select the {int}st/nd/rd/th Cucumber +I select the 3rd Cucumber +[3] diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 9b57b57292..be3d18ed88 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -6,9 +6,11 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; -import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import static java.nio.file.Files.newDirectoryStream; import static org.hamcrest.MatcherAssert.assertThat; @@ -19,8 +21,11 @@ class CucumberExpressionParserTest { private final CucumberExpressionParser parser = new CucumberExpressionParser(); - private static DirectoryStream test() throws IOException { - return newDirectoryStream(Paths.get("testdata", "ast")); + private static List test() throws IOException { + List paths = new ArrayList<>(); + newDirectoryStream(Paths.get("testdata", "ast")).forEach(paths::add); + paths.sort(Comparator.naturalOrder()); + return paths; } @ParameterizedTest diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 88266a6122..3e825b7a00 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -6,9 +6,11 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; -import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.stream.Collectors; import static java.nio.file.Files.newDirectoryStream; @@ -20,8 +22,11 @@ class CucumberExpressionTokenizerTest { private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); - private static DirectoryStream test() throws IOException { - return newDirectoryStream(Paths.get("testdata", "tokens")) ; + private static List test() throws IOException { + List paths = new ArrayList<>(); + newDirectoryStream(Paths.get("testdata", "tokens")).forEach(paths::add); + paths.sort(Comparator.naturalOrder()); + return paths; } @ParameterizedTest diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-parameter.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-parameter.yaml new file mode 100644 index 0000000000..c5daf32bd7 --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/alternation-with-parameter.yaml @@ -0,0 +1,27 @@ +expression: I select the {int}st/nd/rd/th +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "I"}, + {"type": "TEXT_NODE", "start": 1, "end": 2, "token": " "}, + {"type": "TEXT_NODE", "start": 2, "end": 8, "token": "select"}, + {"type": "TEXT_NODE", "start": 8, "end": 9, "token": " "}, + {"type": "TEXT_NODE", "start": 9, "end": 12, "token": "the"}, + {"type": "TEXT_NODE", "start": 12, "end": 13, "token": " "}, + {"type": "PARAMETER_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 14, "end": 17, "token": "int"} + ]}, + {"type": "ALTERNATION_NODE", "start": 18, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 18, "end": 20, "nodes": [ + {"type": "TEXT_NODE", "start": 18, "end": 20, "token": "st"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 21, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 23, "token": "nd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 24, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 24, "end": 26, "token": "rd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 27, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 27, "end": 29, "token": "th"} + ]} + ]} + ]} diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-start-optional.yaml similarity index 100% rename from cucumber-expressions/java/testdata/ast/alternation-with-unused_start-optional.yaml rename to cucumber-expressions/java/testdata/ast/alternation-with-unused-start-optional.yaml From 4da29d8d1f807d96241653d4fa99d7c01047ba07 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 14:00:08 +0200 Subject: [PATCH 122/183] Single quotes for messages --- .../CucumberExpressionException.java | 11 ++++++----- .../cucumberexpressions/CucumberExpressionParser.java | 4 ++-- .../CucumberExpressionTokenizer.java | 5 ++--- .../CucumberExpressionParserTest.java | 2 +- .../CucumberExpressionTokenizerTest.java | 2 +- .../ast/alternation-with-unused-start-optional.yaml | 4 ++-- .../java/testdata/ast/opening-brace.yaml | 6 +++--- .../java/testdata/ast/opening-parenthesis.yaml | 4 ++-- .../java/testdata/ast/unfinished-parameter.yaml | 4 ++-- .../tokens/escape-non-reserved-character.yaml | 6 +++--- .../java/testdata/tokens/escaped-end-of-line.yaml | 4 ++-- 11 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 88766d5ecb..9cfe0cefe8 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -1,7 +1,7 @@ package io.cucumber.cucumberexpressions; -import io.cucumber.cucumberexpressions.Ast.Node; import io.cucumber.cucumberexpressions.Ast.Located; +import io.cucumber.cucumberexpressions.Ast.Node; import io.cucumber.cucumberexpressions.Ast.Token; import io.cucumber.cucumberexpressions.Ast.Token.Type; import org.apiguardian.api.API; @@ -17,7 +17,7 @@ public class CucumberExpressionException extends RuntimeException { super(message, cause); } - static CucumberExpressionException createMissingEndTokenException(String expression, Type beginToken, Type endToken, + static CucumberExpressionException createMissingEndToken(String expression, Type beginToken, Type endToken, Token current) { return new CucumberExpressionException(message( current.start(), @@ -28,11 +28,12 @@ static CucumberExpressionException createMissingEndTokenException(String express .symbol() + "' to escape the " + beginToken.purpose())); } - static CucumberExpressionException createTheEndOfLineCanNotBeEscapedException(String expression) { + static CucumberExpressionException createTheEndOfLineCanNotBeEscaped(String expression) { + int index = expression.codePointCount(0, expression.length()) - 1; return new CucumberExpressionException(message( - expression.length(), + index, expression, - pointAt(expression.length()), + pointAt(index), "The end of line can not be escaped", "You can use '\\\\' to escape the the '\\'" )); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index d6825e6ea7..8bf6ef636f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -22,7 +22,7 @@ import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; import static io.cucumber.cucumberexpressions.Ast.Token.Type.START_OF_LINE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; -import static io.cucumber.cucumberexpressions.CucumberExpressionException.createMissingEndTokenException; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createMissingEndToken; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -155,7 +155,7 @@ private static Parser parseBetween( // endToken not found if (!lookingAt(tokens, subCurrent, endToken)) { - throw createMissingEndTokenException(expression, beginToken, endToken, tokens.get(current)); + throw createMissingEndToken(expression, beginToken, endToken, tokens.get(current)); } // consumes endToken int start = tokens.get(current).start(); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index cea6c5113e..bf3349a83d 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -10,7 +10,7 @@ import java.util.PrimitiveIterator.OfInt; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createCantEscape; -import static io.cucumber.cucumberexpressions.CucumberExpressionException.createTheEndOfLineCanNotBeEscapedException; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createTheEndOfLineCanNotBeEscaped; final class CucumberExpressionTokenizer { @@ -87,7 +87,7 @@ public Token next() { currentTokenType = Type.END_OF_LINE; if (treatAsText) { - throw createTheEndOfLineCanNotBeEscapedException(expression); + throw createTheEndOfLineCanNotBeEscaped(expression); } Token token = convertBufferToToken(currentTokenType); advanceTokenTypes(); @@ -119,7 +119,6 @@ private Type tokenTypeOf(Integer token, boolean treatAsText) { if (Token.canEscape(token)) { return Type.TEXT; } - // Buffer always start at throw createCantEscape(expression, bufferStartIndex + buffer.codePointCount(0, buffer.length()) + escaped); } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index be3d18ed88..32743dbb9b 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -38,7 +38,7 @@ void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation CucumberExpressionException exception = assertThrows( CucumberExpressionException.class, () -> parser.parse(expectation.getExpression())); - assertThat(exception.getMessage(), is(exception.getMessage())); + assertThat(exception.getMessage(), is(expectation.getException())); } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 3e825b7a00..84b3a15d86 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -43,7 +43,7 @@ void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation CucumberExpressionException exception = assertThrows( CucumberExpressionException.class, () -> tokenizer.tokenize(expectation.getExpression())); - assertThat(exception.getMessage(), is(exception.getMessage())); + assertThat(exception.getMessage(), is(expectation.getException())); } } diff --git a/cucumber-expressions/java/testdata/ast/alternation-with-unused-start-optional.yaml b/cucumber-expressions/java/testdata/ast/alternation-with-unused-start-optional.yaml index f25fb0f6cb..e2f0584556 100644 --- a/cucumber-expressions/java/testdata/ast/alternation-with-unused-start-optional.yaml +++ b/cucumber-expressions/java/testdata/ast/alternation-with-unused-start-optional.yaml @@ -4,5 +4,5 @@ exception: |- three blind\ mice/rats( ^ - The "(" does not have a matching ")". - If you did not intend to use optional text you can use "\(" to escape the optional text + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/java/testdata/ast/opening-brace.yaml b/cucumber-expressions/java/testdata/ast/opening-brace.yaml index 2dc64afe10..916a674a36 100644 --- a/cucumber-expressions/java/testdata/ast/opening-brace.yaml +++ b/cucumber-expressions/java/testdata/ast/opening-brace.yaml @@ -1,8 +1,8 @@ -expression: "{" +expression: '{' exception: |- This Cucumber Expression has a problem at column 1: { ^ - The "{" does not have a matching "}". - If you did not intend to use a parameter you can use "\{" to escape the a parameter + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml index 2f3dafc804..929d6ae304 100644 --- a/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml +++ b/cucumber-expressions/java/testdata/ast/opening-parenthesis.yaml @@ -4,5 +4,5 @@ exception: |- ( ^ - The "(" does not have a matching ")". - If you did not intend to use optional text you can use "\(" to escape the optional text + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml index 1688e2c690..d02f9b4ccf 100644 --- a/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml +++ b/cucumber-expressions/java/testdata/ast/unfinished-parameter.yaml @@ -4,5 +4,5 @@ exception: |- {string ^ - The "{" does not have a matching "}". - If you did not intend to use a parameter you can use "\{" to escape the a parameter + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml index ea2998d895..5e206be084 100644 --- a/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml +++ b/cucumber-expressions/java/testdata/tokens/escape-non-reserved-character.yaml @@ -3,6 +3,6 @@ exception: |- This Cucumber Expression has a problem at column 2: \[ - ^ - Only the characters "{", "}", "(", ")", "\", "/" and whitespace can be escaped. - If you did mean to use an "\" you can use "\\" to escape it + ^ + Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. + If you did mean to use an '\' you can use '\\' to escape it diff --git a/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml index 4884e3d122..a1bd00fd98 100644 --- a/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml +++ b/cucumber-expressions/java/testdata/tokens/escaped-end-of-line.yaml @@ -1,8 +1,8 @@ expression: \ exception: |- - This Cucumber Expression has a problem at column 2: + This Cucumber Expression has a problem at column 1: \ ^ The end of line can not be escaped. - You can use "\\" to escape the the "\" + You can use '\\' to escape the the '\' From 9ca64997b343d3ff0bfa1ca8a4f3ed54055cc388 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 14:09:19 +0200 Subject: [PATCH 123/183] All acceptance tests pass --- cucumber-expressions/go/ast.go | 34 ++++++++++ .../go/cucumber_expression_parser.go | 32 ++++----- .../go/cucumber_expression_tokenizer.go | 24 ++++--- cucumber-expressions/go/errors.go | 65 +++++++++++++++++++ 4 files changed, 126 insertions(+), 29 deletions(-) diff --git a/cucumber-expressions/go/ast.go b/cucumber-expressions/go/ast.go index 1e5b01e493..e1ff4c5088 100644 --- a/cucumber-expressions/go/ast.go +++ b/cucumber-expressions/go/ast.go @@ -111,3 +111,37 @@ func typeOf(r rune) (tokenType, error) { } return text, nil } + +func symbol(tokenType tokenType) string { + switch tokenType { + case beginOptional: + return string(beginOptionalCharacter) + case endOptional: + return string(endOptionalCharacter) + case beginParameter: + return string(beginParameterCharacter) + case endParameter: + return string(endParameterCharacter) + case alternation: + return string(alternationCharacter) + } + + return "" +} + +func purpose(tokenType tokenType) string { + switch tokenType { + case beginOptional: + return "optional text" + case endOptional: + return "optional text" + case beginParameter: + return "a parameter" + case endParameter: + return "optional text" + case alternation: + return "alternation" + } + + return "" +} diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 94f139d291..66e33b49d2 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -3,7 +3,7 @@ package cucumberexpressions /* * text := token */ -var textParser = func(tokens []token, current int) (int, node, error) { +var textParser = func(expression string, tokens []token, current int) (int, node, error) { token := tokens[current] return 1, node{textNode, token.Start, token.End, token.Text, nil}, nil } @@ -31,7 +31,7 @@ var optionalParser = parseBetween( ) // alternation := alternative* + ( '/' + alternative* )+ -var alternativeSeparatorParser = func(tokens []token, current int) (int, node, error) { +var alternativeSeparatorParser = func(expression string, tokens []token, current int) (int, node, error) { if !lookingAt(tokens, current, alternation) { return 0, nullNode, nil } @@ -52,13 +52,13 @@ var alternativeParsers = []parser{ * right-boundary := whitespace | { | $ * alternative: = optional | parameter | text */ -var alternationParser = func(tokens []token, current int) (int, node, error) { +var alternationParser = func(expression string, tokens []token, current int) (int, node, error) { previous := current - 1 if !lookingAtAny(tokens, previous, startOfLine, whiteSpace, endParameter) { return 0, nullNode, nil } - consumed, subAst, err := parseTokensUntil(alternativeParsers, tokens, current, whiteSpace, endOfLine, beginParameter) + consumed, subAst, err := parseTokensUntil(expression, alternativeParsers, tokens, current, whiteSpace, endOfLine, beginParameter) if err != nil { return 0, nullNode, err } @@ -100,7 +100,7 @@ func parse(expression string) (node, error) { if err != nil { return nullNode, err } - consumed, ast, err := cucumberExpressionParser(tokens, 0) + consumed, ast, err := cucumberExpressionParser(expression, tokens, 0) if err != nil { return nullNode, err } @@ -111,16 +111,16 @@ func parse(expression string) (node, error) { return ast, nil } -type parser func(tokens []token, current int) (int, node, error) +type parser func(expression string, tokens []token, current int) (int, node, error) func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, parsers ...parser) parser { - return func(tokens []token, current int) (int, node, error) { + return func(expression string, tokens []token, current int) (int, node, error) { if !lookingAt(tokens, current, beginToken) { return 0, nullNode, nil } subCurrent := current + 1 - consumed, subAst, err := parseTokensUntil(parsers, tokens, subCurrent, endToken) + consumed, subAst, err := parseTokensUntil(expression, parsers, tokens, subCurrent, endToken) if err != nil { return 0, nullNode, err } @@ -128,7 +128,7 @@ func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, p // endToken not found if !lookingAt(tokens, subCurrent, endToken) { - return 0, nullNode, NewCucumberExpressionError("No end token") + return 0, nullNode, createMissingEndToken(expression, beginToken, endToken, tokens[current]) } // consumes endToken start := tokens[current].Start @@ -137,17 +137,17 @@ func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, p } } -func parseTokensUntil(parsers []parser, expresion []token, startAt int, endTokens ...tokenType) (int, []node, error) { +func parseTokensUntil(expresion string, parsers []parser, tokens []token, startAt int, endTokens ...tokenType) (int, []node, error) { ast := make([]node, 0) current := startAt - size := len(expresion) + size := len(tokens) for current < size { - if lookingAtAny(expresion, current, endTokens...) { + if lookingAtAny(tokens, current, endTokens...) { break } - consumed, node, err := parseToken(parsers, expresion, current) + consumed, node, err := parseToken(expresion, parsers, tokens, current) if err != nil { - return 0, nil, nil + return 0, nil, err } if consumed == 0 { // If configured correctly this will never happen @@ -161,9 +161,9 @@ func parseTokensUntil(parsers []parser, expresion []token, startAt int, endToken return current - startAt, ast, nil } -func parseToken(parsers []parser, expresion []token, startAt int) (int, node, error) { +func parseToken(expression string, parsers []parser, tokens []token, startAt int) (int, node, error) { for _, parser := range parsers { - consumed, node, err := parser(expresion, startAt) + consumed, node, err := parser(expression, tokens, startAt) if err != nil { return 0, nullNode, err } diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 64567b72da..6216af47dc 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -25,6 +25,16 @@ func tokenize(expression string) ([]token, error) { return t } + tokenTypeOf := func(r rune, treatAsText bool) (tokenType, error) { + if !treatAsText { + return typeOf(r) + } + if canEscape(r) { + return text, nil + } + return startOfLine, createCantEscaped(expression, bufferStartIndex+len(buffer)+escaped) + } + tokens = append(tokens, token{"", startOfLine, 0, 0}) for _, r := range runes { @@ -49,7 +59,6 @@ func tokenize(expression string) ([]token, error) { previousTokenType = currentTokenType buffer = append(buffer, r) } - } if len(buffer) > 0 { @@ -58,20 +67,9 @@ func tokenize(expression string) ([]token, error) { } if treatAsText { - return nil, NewCucumberExpressionError("can't escape EOL") + return nil, createTheEndOfLineCanNotBeEscaped(expression) } token := token{"", endOfLine, len(runes), len(runes)} tokens = append(tokens, token) return tokens, nil } - -func tokenTypeOf(r rune, treatAsText bool) (tokenType, error) { - if !treatAsText { - return typeOf(r) - } - if canEscape(r) { - return text, nil - } - return startOfLine, NewCucumberExpressionError("can't escape") - -} diff --git a/cucumber-expressions/go/errors.go b/cucumber-expressions/go/errors.go index 754a444a6a..8363c9f722 100644 --- a/cucumber-expressions/go/errors.go +++ b/cucumber-expressions/go/errors.go @@ -3,6 +3,7 @@ package cucumberexpressions import ( "fmt" "strings" + "unicode/utf8" ) type CucumberExpressionError struct { @@ -17,6 +18,70 @@ func (e *CucumberExpressionError) Error() string { return e.s } +func createMissingEndToken(expression string, beginToken tokenType, endToken tokenType, current token) error { + return NewCucumberExpressionError(message( + current.Start, + expression, + pointAtToken(current), + "The '"+symbol(beginToken)+"' does not have a matching '"+symbol(endToken)+"'", + "If you did not intend to use "+purpose(beginToken)+" you can use '\\"+symbol(beginToken)+"' to escape the "+purpose(beginToken), + )) +} + +func createTheEndOfLineCanNotBeEscaped(expression string) error { + index := utf8.RuneCountInString(expression) - 1 + return NewCucumberExpressionError(message( + index, + expression, + pointAt(index), + "The end of line can not be escaped", + "You can use '\\\\' to escape the the '\\'", + )) +} + +func createCantEscaped(expression string, index int) error { + return NewCucumberExpressionError(message( + index, + expression, + pointAt(index), + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped", + "If you did mean to use an '\\' you can use '\\\\' to escape it", + )) +} + +func pointAt(index int) strings.Builder { + pointer := strings.Builder{} + for i := 0; i < index; i++ { + pointer.WriteString(" ") + } + pointer.WriteString("^") + return pointer +} + +func pointAtToken(node token) strings.Builder { + pointer := pointAt(node.Start) + if node.Start+1 < node.End { + for i := node.Start + 1; i < node.End-1; i++ { + pointer.WriteString("-") + } + pointer.WriteString("^") + } + return pointer +} + +func message(index int, expression string, pointer strings.Builder, problem string, solution string) string { + return thisCucumberExpressionHasAProblemAt(index) + + "\n" + + expression + "\n" + + pointer.String() + "\n" + + problem + ".\n" + + solution +} + +func thisCucumberExpressionHasAProblemAt(index int) string { + return fmt.Sprintf("This Cucumber Expression has a problem at column %d:\n", index+1) +} + type AmbiguousParameterTypeError struct { s string } From 5d3197f908d3c161b90a6386e39ec93f6db241b4 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 17:26:50 +0200 Subject: [PATCH 124/183] Extract acceptance tests for CucumberExpressions --- .../CucumberExpressionTest.java | 385 +++--------------- .../cucumberexpressions/Expectation.java | 14 +- ...lows-escaped-optional-parameter-types.yaml | 4 + ...llows-parameter-type-in-alternation-1.yaml | 4 + ...llows-parameter-type-in-alternation-2.yaml | 4 + ...low-parameter-adjacent-to-alternation.yaml | 5 + ...lternative-by-adjacent-left-parameter.yaml | 10 + ...mpty-alternative-by-adjacent-optional.yaml | 9 + ...ternative-by-adjacent-right-parameter.yaml | 9 + ...ow-alternation-with-empty-alternative.yaml | 9 + .../does-not-allow-empty-optional.yaml | 9 + ...es-not-allow-optional-parameter-types.yaml | 9 + ...llow-parameter-type-with-left-bracket.yaml | 5 + .../does-not-match-misquoted-string.yaml | 4 + .../expression/doesnt-match-float-as-int.yaml | 5 + ...tches-alternation-in-optional-as-text.yaml | 4 + .../expression/matches-alternation.yaml | 4 + .../matches-anonymous-parameter-type.yaml | 5 + ...empty-string-along-with-other-strings.yaml | 4 + ...e-quoted-empty-string-as-empty-string.yaml | 4 + ...oted-string-with-escaped-double-quote.yaml | 4 + ...uble-quoted-string-with-single-quotes.yaml | 4 + .../matches-double-quoted-string.yaml | 4 + .../matches-doubly-escaped-parenthesis.yaml | 4 + .../matches-doubly-escaped-slash-1.yaml | 4 + .../matches-doubly-escaped-slash-2.yaml | 4 + .../matches-escaped-parenthesis-1.yaml | 4 + .../matches-escaped-parenthesis-2.yaml | 4 + .../matches-escaped-parenthesis-3.yaml | 4 + .../expression/matches-escaped-slash.yaml | 4 + .../testdata/expression/matches-float-1.yaml | 5 + .../testdata/expression/matches-float-2.yaml | 5 + .../java/testdata/expression/matches-int.yaml | 5 + ...atches-multiple-double-quoted-strings.yaml | 4 + ...atches-multiple-single-quoted-strings.yaml | 4 + ...matches-optional-before-alternation-1.yaml | 4 + ...matches-optional-before-alternation-2.yaml | 4 + ...e-alternation-with-regex-characters-1.yaml | 4 + ...e-alternation-with-regex-characters-2.yaml | 4 + .../matches-optional-in-alternation-1.yaml | 5 + .../matches-optional-in-alternation-2.yaml | 5 + .../matches-optional-in-alternation-3.yaml | 5 + ...empty-string-along-with-other-strings.yaml | 4 + ...e-quoted-empty-string-as-empty-string.yaml | 4 + ...ngle-quoted-string-with-double-quotes.yaml | 4 + ...oted-string-with-escaped-single-quote.yaml | 4 + .../matches-single-quoted-string.yaml | 4 + .../testdata/expression/matches-word.yaml | 4 + .../throws-unknown-parameter-type.yaml | 5 + 49 files changed, 300 insertions(+), 329 deletions(-) create mode 100644 cucumber-expressions/java/testdata/expression/allows-escaped-optional-parameter-types.yaml create mode 100644 cucumber-expressions/java/testdata/expression/allows-parameter-type-in-alternation-1.yaml create mode 100644 cucumber-expressions/java/testdata/expression/allows-parameter-type-in-alternation-2.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-empty-optional.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-optional-parameter-types.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-match-misquoted-string.yaml create mode 100644 cucumber-expressions/java/testdata/expression/doesnt-match-float-as-int.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-alternation-in-optional-as-text.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-alternation.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-anonymous-parameter-type.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-double-quoted-string.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-doubly-escaped-parenthesis.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-doubly-escaped-slash-1.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-doubly-escaped-slash-2.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-1.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-2.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-3.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-escaped-slash.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-float-1.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-float-2.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-int.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-multiple-double-quoted-strings.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-multiple-single-quoted-strings.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-1.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-2.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-1.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-2.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-3.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-single-quoted-string.yaml create mode 100644 cucumber-expressions/java/testdata/expression/matches-word.yaml create mode 100644 cucumber-expressions/java/testdata/expression/throws-unknown-parameter-type.yaml diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 100df90b9a..0b629ad793 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -1,230 +1,68 @@ package io.cucumber.cucumberexpressions; +import com.google.gson.Gson; +import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.MethodSource; +import java.io.IOException; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; +import static java.nio.file.Files.newDirectoryStream; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -public class CucumberExpressionTest { +class CucumberExpressionTest { private final ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); - @Test - public void documents_match_arguments() { - String expr = "I have {int} cuke(s)"; - Expression expression = new CucumberExpression(expr, parameterTypeRegistry); - List> args = expression.match("I have 7 cukes"); - assertEquals(7, args.get(0).getValue()); - } - - @Test - public void matches_word() { - assertEquals(singletonList("blind"), match("three {word} mice", "three blind mice")); - } - - @Test - public void matches_alternation() { - assertEquals(emptyList(), match("mice/rats and rats\\/mice", "rats and rats/mice")); - } - - @Test - public void matches_optional_before_alternation_1() { - assertEquals(emptyList(), match("three (brown )mice/rats", "three brown mice")); - } - - @Test - public void matches_optional_before_alternation_2() { - assertEquals(emptyList(), match("three (brown )mice/rats", "three rats")); - } - - @Test - public void matches_optional_in_alternation_1() { - assertEquals(singletonList(3), match("{int} rat(s)/mouse/mice", "3 rats")); - } - - @Test - public void matches_optional_in_alternation_2() { - assertEquals(singletonList(2), match("{int} rat(s)/mouse/mice", "2 mice")); - } - - @Test - public void matches_optional_in_alternation_3() { - assertEquals(singletonList(1), match("{int} rat(s)/mouse/mice", "1 mouse")); - } - - @Test - public void matches_optional_before_alternation_with_regex_characters_1() { - assertEquals(singletonList(2), match("I wait {int} second(s)./second(s)?", "I wait 2 seconds?")); - } - - @Test - public void matches_optional_before_alternation_with_regex_characters_2() { - assertEquals(singletonList(1), match("I wait {int} second(s)./second(s)?", "I wait 1 second.")); - } - - @Test - public void matches_alternation_in_optional_as_text() { - assertEquals(emptyList(), match("three( brown/black) mice", "three brown/black mice")); - } - - @Test - public void does_not_allow_empty_optional() { - Executable testMethod = () -> match("three () mice", "three brown mice"); - CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has a problem at column 7:\n" + - "\n" + - "three () mice\n" + - " ^^\n" + - "An optional must contain some text.\n" + - "If you did not mean to use an optional you can use '\\(' to escape the the '('"))); - } - - @Test - public void does_not_allow_alternation_with_empty_alternative() { - Executable testMethod = () -> match("three brown//black mice", "three brown mice"); - CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("This Cucumber Expression has a problem at column 13:\n" + - "\n" + - "three brown//black mice\n" + - " ^\n" + - "Alternative may not be empty." + - "\nIf you did not mean to use an alternative you can use '\\/' to escape the the '/'"))); - } - - @Test - public void does_not_allow_alternation_with_empty_alternative_by_adjacent_optional() { - Executable testMethod = () -> match("three (brown)/black mice", "three brown mice"); - CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), - is(equalTo("" + - "This Cucumber Expression has a problem at column 7:\n" + - "\n" + - "three (brown)/black mice\n" + - " ^-----^\n" + - "An alternative may not exclusively contain optionals.\n" + - "If you did not mean to use an optional you can use '\\(' to escape the the '('"))); - } - - @Test - public void does_not_allow_alternation_with_empty_alternative_by_adjacent_right_parameter() { - - final Executable testMethod = () -> match("x/{int}", "3"); - - final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has a problem at column 3:\n" + - "\n" + - "x/{int}\n" + - " ^\n" + - "Alternative may not be empty.\n" + - "If you did not mean to use an alternative you can use '\\/' to escape the the '/'"))); - } - - @Test - public void does_allow_parameter_adjacent_to_alternation() { - assertEquals(singletonList(3), match("{int}st/nd/rd/th", "3rd")); - } - - @Test - public void does_not_allow_alternation_with_empty_alternative_by_adjacent_left_parameter() { - - final Executable testMethod = () -> match("{int}/x", "3"); - - final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has a problem at column 6:\n" + - "\n" + - "{int}/x\n" + - " ^\n" + - "Alternative may not be empty.\n" + - "If you did not mean to use an alternative you can use '\\/' to escape the the '/'"))); - } - - @Test - public void matches_double_quoted_string() { - assertEquals(singletonList("blind"), match("three {string} mice", "three \"blind\" mice")); - } - - @Test - public void matches_multiple_double_quoted_strings() { - assertEquals(asList("blind", "crippled"), - match("three {string} and {string} mice", "three \"blind\" and \"crippled\" mice")); - } - - @Test - public void matches_single_quoted_string() { - assertEquals(singletonList("blind"), match("three {string} mice", "three 'blind' mice")); - } - - @Test - public void matches_multiple_single_quoted_strings() { - assertEquals(asList("blind", "crippled"), - match("three {string} and {string} mice", "three 'blind' and 'crippled' mice")); - } - - @Test - public void does_not_match_misquoted_string() { - assertNull(match("three {string} mice", "three \"blind' mice")); - } - - @Test - public void matches_single_quoted_string_with_double_quotes() { - assertEquals(singletonList("\"blind\""), match("three {string} mice", "three '\"blind\"' mice")); - } - - @Test - public void matches_double_quoted_string_with_single_quotes() { - assertEquals(singletonList("'blind'"), match("three {string} mice", "three \"'blind'\" mice")); - } - - @Test - public void matches_double_quoted_string_with_escaped_double_quote() { - assertEquals(singletonList("bl\"nd"), match("three {string} mice", "three \"bl\\\"nd\" mice")); - } - - @Test - public void matches_single_quoted_string_with_escaped_single_quote() { - assertEquals(singletonList("bl'nd"), match("three {string} mice", "three 'bl\\'nd' mice")); - } - - @Test - public void matches_single_quoted_empty_string_as_empty_string() { - assertEquals(singletonList(""), match("three {string} mice", "three '' mice")); - } - - @Test - public void matches_double_quoted_empty_string_as_empty_string() { - assertEquals(singletonList(""), match("three {string} mice", "three \"\" mice")); - } - - @Test - public void matches_single_quoted_empty_string_as_empty_string_along_with_other_strings() { - assertEquals(asList("", "handsome"), match("three {string} and {string} mice", "three '' and 'handsome' mice")); + private static List test() throws IOException { + List paths = new ArrayList<>(); + newDirectoryStream(Paths.get("testdata", "expression")).forEach(paths::add); + paths.sort(Comparator.naturalOrder()); + return paths; + } + + @ParameterizedTest + @MethodSource + void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { + if (expectation.getException() == null) { + CucumberExpression expression = new CucumberExpression(expectation.getExpression(), parameterTypeRegistry); + List> match = expression.match(expectation.getText()); + List values = match == null ? null : match.stream() + .map(Argument::getValue) + .collect(Collectors.toList()); + assertEquals(expectation.getExpected(), new Gson().toJson(values)); + } else { + Executable executable = () -> { + String expr = expectation.getExpression(); + CucumberExpression expression = new CucumberExpression(expr, parameterTypeRegistry); + expression.match(expectation.getText()); + }; + CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, executable); + assertThat(exception.getMessage(), Matchers.is(expectation.getException())); + } } - @Test - public void matches_double_quoted_empty_string_as_empty_string_along_with_other_strings() { - assertEquals(asList("", "handsome"), - match("three {string} and {string} mice", "three \"\" and \"handsome\" mice")); - } + // Misc tests @Test - public void alternation_seperator_can_be_used_in_parameter() { + void alternation_separator_can_be_used_in_parameter() { parameterTypeRegistry .defineParameterType(new ParameterType<>("a/b", "(.*)", String.class, (String arg) -> arg)); assertEquals(singletonList("three mice"), @@ -232,25 +70,7 @@ public void alternation_seperator_can_be_used_in_parameter() { } @Test - public void matches_escaped_parenthesis_1() { - assertEquals(emptyList(), - match("three \\(exceptionally) \\{string} mice", "three (exceptionally) {string} mice")); - } - - @Test - public void matches_escaped_parenthesis_2() { - assertEquals(singletonList("blind"), - match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); - } - - @Test - public void matches_escaped_parenthesis_3() { - assertEquals(singletonList("blind"), - match("three \\((exceptionally)) \\{{string}} mice", "three (exceptionally) {\"blind\"} mice")); - } - - @Test - public void matches_escaped_parenthesis_4() { + void matches_escaped_parenthesis_4() { parameterTypeRegistry .defineParameterType(new ParameterType<>("{string}", "\"(.*)\"", String.class, (String arg) -> arg)); assertEquals(singletonList("blind"), @@ -258,156 +78,67 @@ public void matches_escaped_parenthesis_4() { } @Test - public void matches_doubly_escaped_parenthesis() { - assertEquals(singletonList("blind"), - match("three \\\\(exceptionally) \\\\{string} mice", "three \\exceptionally \\\"blind\" mice")); - } - - @Test - public void matches_escaped_slash() { - assertEquals(emptyList(), match("12\\/2020", "12/2020")); - } - - @Test - public void matches_doubly_escaped_slash_1() { - assertEquals(emptyList(), match("12\\\\/2020", "12\\")); - } - - @Test - public void matches_doubly_escaped_slash_2() { - assertEquals(emptyList(), match("12\\\\/2020", "2020")); - } - - @Test - public void matches_int() { - assertEquals(singletonList(22), match("{int}", "22")); - } - - @Test - public void doesnt_match_float_as_int() { - assertNull(match("{int}", "1.22")); - } - - @Test - public void matches_float_1() { - assertEquals(singletonList(0.22f), match("{float}", "0.22")); - } - - @Test - public void matches_float_2() { - assertEquals(singletonList(0.22f), match("{float}", ".22")); + void exposes_source() { + String expr = "I have {int} cuke(s)"; + assertEquals(expr, new CucumberExpression(expr, new ParameterTypeRegistry(Locale.ENGLISH)).getSource()); } + // Java-specific @Test - public void matches_anonymous_parameter_type_with_hint() { + void matches_anonymous_parameter_type_with_hint() { assertEquals(singletonList(0.22f), match("{}", "0.22", Float.class)); } @Test - public void matches_anonymous_parameter_type() { - assertEquals(singletonList("0.22"), match("{}", "0.22")); - } - - @Test - public void does_not_allow_parameter_type_with_left_bracket() { - - final Executable testMethod = () -> match("{[string]}", "something"); - - final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat("Unexpected message", thrownException.getMessage(), - is(equalTo("Illegal character '[' in parameter name {[string]}."))); - } - - @Test - public void throws_unknown_parameter_type() { - - final Executable testMethod = () -> match("{unknown}", "something"); - - final UndefinedParameterTypeException thrownException = assertThrows(UndefinedParameterTypeException.class, - testMethod); - assertThat(thrownException.getMessage(), - is(equalTo("Undefined parameter type {unknown}. Please register a ParameterType for {unknown}."))); - } - - @Test - public void does_not_allow_optional_parameter_types() { - - final Executable testMethod = () -> match("({int})", "3"); - - final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("" + - "This Cucumber Expression has a problem at column 2:\n" + - "\n" + - "({int})\n" + - " ^---^\n" + - "An optional may not contain a parameter type.\n" + - "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'"))); - } - - @Test - public void allows_escaped_optional_parameter_types() { - assertEquals(singletonList(3), match("\\({int})", "(3)")); - } - - @Test - public void allows_parameter_type_in_alternation_1() { - assertEquals(singletonList(18), match("a/i{int}n/y", "i18n")); - } - @Test - public void allows_parameter_type_in_alternation_2() { - assertEquals(singletonList(11), match("a/i{int}n/y", "a11y")); - } - - @Test - public void exposes_source() { + void documents_match_arguments() { String expr = "I have {int} cuke(s)"; - assertEquals(expr, new CucumberExpression(expr, new ParameterTypeRegistry(Locale.ENGLISH)).getSource()); + Expression expression = new CucumberExpression(expr, parameterTypeRegistry); + List> args = expression.match("I have 7 cukes"); + assertEquals(7, args.get(0).getValue()); } - // Java-specific - @Test - public void matches_byte() { + void matches_byte() { assertEquals(singletonList(Byte.MAX_VALUE), match("{byte}", "127")); } @Test - public void matches_short() { + void matches_short() { assertEquals(singletonList(Short.MAX_VALUE), match("{short}", String.valueOf(Short.MAX_VALUE))); } @Test - public void matches_long() { + void matches_long() { assertEquals(singletonList(Long.MAX_VALUE), match("{long}", String.valueOf(Long.MAX_VALUE))); } @Test - public void matches_biginteger() { + void matches_biginteger() { BigInteger bigInteger = BigInteger.valueOf(Long.MAX_VALUE); bigInteger = bigInteger.pow(10); assertEquals(singletonList(bigInteger), match("{biginteger}", bigInteger.toString())); } @Test - public void matches_bigdecimal() { + void matches_bigdecimal() { BigDecimal bigDecimal = BigDecimal.valueOf(Math.PI); assertEquals(singletonList(bigDecimal), match("{bigdecimal}", bigDecimal.toString())); } @Test - public void matches_double_with_comma_for_locale_using_comma() { + void matches_double_with_comma_for_locale_using_comma() { List values = match("{double}", "1,22", Locale.FRANCE); assertEquals(singletonList(1.22), values); } @Test - public void matches_float_with_zero() { + void matches_float_with_zero() { List values = match("{float}", "0", Locale.ENGLISH); assertEquals(0.0f, values.get(0)); } @Test - public void unmatched_optional_groups_have_null_values() { + void unmatched_optional_groups_have_null_values() { ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); parameterTypeRegistry.defineParameterType(new ParameterType<>( "textAndOrNumber", diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java index 7a9e2c2e0a..6f865b2d7e 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/Expectation.java @@ -4,16 +4,17 @@ class Expectation { String expression; + String text; String expected; String exception; - Expectation(String expression, String expected, String exception) { + Expectation(String expression, String text, String expected, String exception) { this.expression = expression; + this.text = text; this.expected = expected; this.exception = exception; } - public String getExpression() { return expression; } @@ -26,6 +27,7 @@ public String getException() { public static Expectation fromMap(Map map) { return new Expectation( (String) map.get("expression"), + (String) map.get("text"), (String) map.get("expected"), (String) map.get("exception")); } @@ -46,4 +48,12 @@ public void setExpected(String expected) { this.expected = expected; } + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } diff --git a/cucumber-expressions/java/testdata/expression/allows-escaped-optional-parameter-types.yaml b/cucumber-expressions/java/testdata/expression/allows-escaped-optional-parameter-types.yaml new file mode 100644 index 0000000000..a00b45acef --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/allows-escaped-optional-parameter-types.yaml @@ -0,0 +1,4 @@ +expression: \({int}) +text: (3) +expected: |- + [3] diff --git a/cucumber-expressions/java/testdata/expression/allows-parameter-type-in-alternation-1.yaml b/cucumber-expressions/java/testdata/expression/allows-parameter-type-in-alternation-1.yaml new file mode 100644 index 0000000000..bb1a6f21b1 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/allows-parameter-type-in-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: i18n +expected: |- + [18] diff --git a/cucumber-expressions/java/testdata/expression/allows-parameter-type-in-alternation-2.yaml b/cucumber-expressions/java/testdata/expression/allows-parameter-type-in-alternation-2.yaml new file mode 100644 index 0000000000..cdddce7d84 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/allows-parameter-type-in-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: a11y +expected: |- + [11] diff --git a/cucumber-expressions/java/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml b/cucumber-expressions/java/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml new file mode 100644 index 0000000000..9e2ecdfbe1 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml @@ -0,0 +1,5 @@ +expression: |- + {int}st/nd/rd/th +text: 3rd +expected: |- + [3] diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml new file mode 100644 index 0000000000..b32540a4a9 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml @@ -0,0 +1,10 @@ +expression: |- + {int}/x +text: '3' +exception: |- + This Cucumber Expression has a problem at column 6: + + {int}/x + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml new file mode 100644 index 0000000000..a0aab0e5a9 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml @@ -0,0 +1,9 @@ +expression: three (brown)/black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (brown)/black mice + ^-----^ + An alternative may not exclusively contain optionals. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml new file mode 100644 index 0000000000..50250f00aa --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml @@ -0,0 +1,9 @@ +expression: x/{int} +text: '3' +exception: |- + This Cucumber Expression has a problem at column 3: + + x/{int} + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml new file mode 100644 index 0000000000..b724cfa77f --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml @@ -0,0 +1,9 @@ +expression: three brown//black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three brown//black mice + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-empty-optional.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-empty-optional.yaml new file mode 100644 index 0000000000..00e341af0b --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-empty-optional.yaml @@ -0,0 +1,9 @@ +expression: three () mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three () mice + ^^ + An optional must contain some text. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-optional-parameter-types.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-optional-parameter-types.yaml new file mode 100644 index 0000000000..b88061e9b4 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-optional-parameter-types.yaml @@ -0,0 +1,9 @@ +expression: ({int}) +text: '3' +exception: |- + This Cucumber Expression has a problem at column 2: + + ({int}) + ^---^ + An optional may not contain a parameter type. + If you did not mean to use an parameter type you can use '\{' to escape the the '{' diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml new file mode 100644 index 0000000000..9c64faaaed --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml @@ -0,0 +1,5 @@ +expression: |- + {[string]} +text: something +exception: |- + Illegal character '[' in parameter name {[string]}. diff --git a/cucumber-expressions/java/testdata/expression/does-not-match-misquoted-string.yaml b/cucumber-expressions/java/testdata/expression/does-not-match-misquoted-string.yaml new file mode 100644 index 0000000000..18023180af --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-match-misquoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind' mice +expected: |- + null diff --git a/cucumber-expressions/java/testdata/expression/doesnt-match-float-as-int.yaml b/cucumber-expressions/java/testdata/expression/doesnt-match-float-as-int.yaml new file mode 100644 index 0000000000..d66b586430 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/doesnt-match-float-as-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '1.22' +expected: |- + null diff --git a/cucumber-expressions/java/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/java/testdata/expression/matches-alternation-in-optional-as-text.yaml new file mode 100644 index 0000000000..15fe78bf53 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-alternation-in-optional-as-text.yaml @@ -0,0 +1,4 @@ +expression: three( brown/black) mice +text: three brown/black mice +expected: |- + [] diff --git a/cucumber-expressions/java/testdata/expression/matches-alternation.yaml b/cucumber-expressions/java/testdata/expression/matches-alternation.yaml new file mode 100644 index 0000000000..20a9b9a728 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-alternation.yaml @@ -0,0 +1,4 @@ +expression: mice/rats and rats\/mice +text: rats and rats/mice +expected: |- + [] diff --git a/cucumber-expressions/java/testdata/expression/matches-anonymous-parameter-type.yaml b/cucumber-expressions/java/testdata/expression/matches-anonymous-parameter-type.yaml new file mode 100644 index 0000000000..fc954960df --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-anonymous-parameter-type.yaml @@ -0,0 +1,5 @@ +expression: |- + {} +text: '0.22' +expected: |- + ["0.22"] diff --git a/cucumber-expressions/java/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/java/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c3e1962e22 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "" and "handsome" mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/java/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/java/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..89315b62ef --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "" mice +expected: |- + [""] diff --git a/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml b/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml new file mode 100644 index 0000000000..fe30a044c1 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "bl\"nd" mice +expected: |- + ["bl\"nd"] diff --git a/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml b/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml new file mode 100644 index 0000000000..d4467ed484 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "'blind'" mice +expected: |- + ["\u0027blind\u0027"] diff --git a/cucumber-expressions/java/testdata/expression/matches-double-quoted-string.yaml b/cucumber-expressions/java/testdata/expression/matches-double-quoted-string.yaml new file mode 100644 index 0000000000..8b0560f332 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-double-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-parenthesis.yaml b/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-parenthesis.yaml new file mode 100644 index 0000000000..902a084103 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-parenthesis.yaml @@ -0,0 +1,4 @@ +expression: three \\(exceptionally) \\{string} mice +text: three \exceptionally \"blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-slash-1.yaml b/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-slash-1.yaml new file mode 100644 index 0000000000..94e531eca7 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-slash-1.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: 12\ +expected: |- + [] diff --git a/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-slash-2.yaml b/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-slash-2.yaml new file mode 100644 index 0000000000..9c9c735b86 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-doubly-escaped-slash-2.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: '2020' +expected: |- + [] diff --git a/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-1.yaml b/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-1.yaml new file mode 100644 index 0000000000..171df89ee1 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-1.yaml @@ -0,0 +1,4 @@ +expression: three \(exceptionally) \{string} mice +text: three (exceptionally) {string} mice +expected: |- + [] diff --git a/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-2.yaml b/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-2.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-2.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-3.yaml b/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-3.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-escaped-parenthesis-3.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/java/testdata/expression/matches-escaped-slash.yaml b/cucumber-expressions/java/testdata/expression/matches-escaped-slash.yaml new file mode 100644 index 0000000000..d8b3933399 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-escaped-slash.yaml @@ -0,0 +1,4 @@ +expression: 12\/2020 +text: 12/2020 +expected: |- + [] diff --git a/cucumber-expressions/java/testdata/expression/matches-float-1.yaml b/cucumber-expressions/java/testdata/expression/matches-float-1.yaml new file mode 100644 index 0000000000..fe7e8b1869 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-float-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '0.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/java/testdata/expression/matches-float-2.yaml b/cucumber-expressions/java/testdata/expression/matches-float-2.yaml new file mode 100644 index 0000000000..c1e5894eac --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-float-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/java/testdata/expression/matches-int.yaml b/cucumber-expressions/java/testdata/expression/matches-int.yaml new file mode 100644 index 0000000000..bcd3763886 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '22' +expected: |- + [22] diff --git a/cucumber-expressions/java/testdata/expression/matches-multiple-double-quoted-strings.yaml b/cucumber-expressions/java/testdata/expression/matches-multiple-double-quoted-strings.yaml new file mode 100644 index 0000000000..6c74bc2350 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-multiple-double-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "blind" and "crippled" mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/java/testdata/expression/matches-multiple-single-quoted-strings.yaml b/cucumber-expressions/java/testdata/expression/matches-multiple-single-quoted-strings.yaml new file mode 100644 index 0000000000..5037821c14 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-multiple-single-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three 'blind' and 'crippled' mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-1.yaml b/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-1.yaml new file mode 100644 index 0000000000..821776715b --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three brown mice +expected: |- + [] diff --git a/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-2.yaml b/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-2.yaml new file mode 100644 index 0000000000..71b3a341f1 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three rats +expected: |- + [] diff --git a/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml b/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml new file mode 100644 index 0000000000..2632f410ce --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 2 seconds? +expected: |- + [2] diff --git a/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml b/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml new file mode 100644 index 0000000000..7b30f667bc --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 1 second. +expected: |- + [1] diff --git a/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-1.yaml b/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-1.yaml new file mode 100644 index 0000000000..6574bb4bdf --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 3 rats +expected: |- + [3] diff --git a/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-2.yaml b/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-2.yaml new file mode 100644 index 0000000000..4eb0f0e1c6 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 2 mice +expected: |- + [2] diff --git a/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-3.yaml b/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-3.yaml new file mode 100644 index 0000000000..964fa6489e --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-optional-in-alternation-3.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 1 mouse +expected: |- + [1] diff --git a/cucumber-expressions/java/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/java/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c963dcf1c7 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three '' and 'handsome' mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/java/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/java/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..cff2a2d1ec --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '' mice +expected: |- + [""] diff --git a/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml b/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml new file mode 100644 index 0000000000..eb9ed537cd --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '"blind"' mice +expected: |- + ["\"blind\""] diff --git a/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml b/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml new file mode 100644 index 0000000000..0b7a917c8f --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'bl\'nd' mice +expected: |- + ["bl\u0027nd"] diff --git a/cucumber-expressions/java/testdata/expression/matches-single-quoted-string.yaml b/cucumber-expressions/java/testdata/expression/matches-single-quoted-string.yaml new file mode 100644 index 0000000000..6c8f4652a5 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-single-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'blind' mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/java/testdata/expression/matches-word.yaml b/cucumber-expressions/java/testdata/expression/matches-word.yaml new file mode 100644 index 0000000000..358fd3afd1 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/matches-word.yaml @@ -0,0 +1,4 @@ +expression: three {word} mice +text: three blind mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/java/testdata/expression/throws-unknown-parameter-type.yaml b/cucumber-expressions/java/testdata/expression/throws-unknown-parameter-type.yaml new file mode 100644 index 0000000000..3061f3e1bb --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/throws-unknown-parameter-type.yaml @@ -0,0 +1,5 @@ +expression: |- + {unknown} +text: something +exception: |- + Undefined parameter type {unknown}. Please register a ParameterType for {unknown}. From a9d5d2ca06a68bddbc7a50f151bc5b5ea4d621da Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 22:50:47 +0200 Subject: [PATCH 125/183] Improve illegal character in parameter name message --- .../CucumberExpression.java | 13 ++- .../CucumberExpressionException.java | 17 ++++ .../cucumberexpressions/ParameterType.java | 89 +++++++++++++------ .../CustomParameterTypeTest.java | 2 +- ...llow-parameter-type-with-left-bracket.yaml | 7 +- 5 files changed, 96 insertions(+), 32 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 879322ada3..a63eeeb13a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -13,6 +13,7 @@ import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeIsEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayExclusivelyContainOptionals; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createInvalidParameterTypeName; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInOptional; import static java.util.stream.Collectors.joining; @@ -88,8 +89,7 @@ private String rewriteAlternative(Node node) { } private String rewriteParameter(Node node) { - String name = node.text(); - ParameterType.checkParameterTypeName(name); + String name = assertValidParameterTypeName(node, astNode -> createInvalidParameterTypeName(astNode, source)); ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); if (parameterType == null) { throw new UndefinedParameterTypeException(name); @@ -103,12 +103,21 @@ private String rewriteParameter(Node node) { .collect(joining(")|(?:", "((?:", "))")); } + private String rewriteExpression(Node node) { return node.nodes().stream() .map(this::rewriteToRegex) .collect(joining("", "^", "$")); } + private String assertValidParameterTypeName(Node node, Function createParameterTypeWasNotValidException) { + String name = node.text(); + if (!ParameterType.isValidParameterTypeName(name)) { + throw createParameterTypeWasNotValidException.apply(node); + } + return name; + } + private void assertNotEmpty(Node node, Function createNodeWasNotEmptyException) { node.nodes() diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 9cfe0cefe8..be92754c14 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -89,6 +89,21 @@ static CucumberExpressionException createCantEscape(String expression, int index "If you did mean to use an '\\' you can use '\\\\' to escape it")); } + static CucumberExpressionException createInvalidParameterTypeName(String name) { + return new CucumberExpressionException( + "Illegal character in parameter name {" + name + "}. Parameter names may not contain '[]()$.|?*+'"); + } + + static CucumberExpressionException createInvalidParameterTypeName(Node node, String expression) { + return new CucumberExpressionException(message( + node.start(), + expression, + pointAt(node), + "Parameter names may not contain '[]()$.|?*+'", + "Did you mean to use a regular expression?")); + } + + private static String message(int index, String expression, StringBuilder pointer, String problem, String solution) { return thisCucumberExpressionHasAProblemAt(index) + @@ -119,4 +134,6 @@ private static StringBuilder pointAt(int index) { return pointer; } + + } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java index 938c7a5b2b..06538791e8 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java @@ -25,11 +25,15 @@ public final class ParameterType implements Comparable> { private final boolean useRegexpMatchAsStrongTypeHint; static void checkParameterTypeName(String name) { + if (!isValidParameterTypeName(name)) { + throw CucumberExpressionException.createInvalidParameterTypeName(name); + } + } + + static boolean isValidParameterTypeName(String name) { String unescapedTypeName = UNESCAPE_PATTERN.matcher(name).replaceAll("$2"); Matcher matcher = ILLEGAL_PARAMETER_NAME_PATTERN.matcher(unescapedTypeName); - if (matcher.find()) { - throw new CucumberExpressionException(String.format("Illegal character '%s' in parameter name {%s}.", matcher.group(1), unescapedTypeName)); - } + return !matcher.find(); } static ParameterType createAnonymousParameterType(String regexp) { @@ -46,7 +50,8 @@ static ParameterType fromEnum(final Class enumClass) { Enum[] enumConstants = enumClass.getEnumConstants(); StringBuilder regexpBuilder = new StringBuilder(); for (int i = 0; i < enumConstants.length; i++) { - if (i > 0) regexpBuilder.append("|"); + if (i > 0) + regexpBuilder.append("|"); regexpBuilder.append(enumConstants[i].name()); } return new ParameterType<>( @@ -57,11 +62,17 @@ static ParameterType fromEnum(final Class enumClass) { ); } - private ParameterType(String name, List regexps, Type type, CaptureGroupTransformer transformer, boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint, boolean anonymous) { - if (regexps == null) throw new NullPointerException("regexps cannot be null"); - if (type == null) throw new NullPointerException("type cannot be null"); - if (transformer == null) throw new NullPointerException("transformer cannot be null"); - if (name != null) checkParameterTypeName(name); + private ParameterType(String name, List regexps, Type type, CaptureGroupTransformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint, + boolean anonymous) { + if (regexps == null) + throw new NullPointerException("regexps cannot be null"); + if (type == null) + throw new NullPointerException("type cannot be null"); + if (transformer == null) + throw new NullPointerException("transformer cannot be null"); + if (name != null) + checkParameterTypeName(name); this.name = name; this.regexps = regexps; this.type = type; @@ -72,11 +83,14 @@ private ParameterType(String name, List regexps, Type type, CaptureGroup this.useRegexpMatchAsStrongTypeHint = useRegexpMatchAsStrongTypeHint; } - public ParameterType(String name, List regexps, Type type, CaptureGroupTransformer transformer, boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint) { - this(name, regexps, type, transformer, useForSnippets, preferForRegexpMatch, useRegexpMatchAsStrongTypeHint, false); + public ParameterType(String name, List regexps, Type type, CaptureGroupTransformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint) { + this(name, regexps, type, transformer, useForSnippets, preferForRegexpMatch, useRegexpMatchAsStrongTypeHint, + false); } - public ParameterType(String name, List regexps, Type type, CaptureGroupTransformer transformer, boolean useForSnippets, boolean preferForRegexpMatch) { + public ParameterType(String name, List regexps, Type type, CaptureGroupTransformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch) { // Unless explicitly set useRegexpMatchAsStrongTypeHint is true. // // Reasoning: @@ -96,11 +110,13 @@ public ParameterType(String name, List regexps, Type type, CaptureGroupT this(name, regexps, type, transformer, useForSnippets, preferForRegexpMatch, true); } - public ParameterType(String name, List regexps, Class type, CaptureGroupTransformer transformer, boolean useForSnippets, boolean preferForRegexpMatch) { + public ParameterType(String name, List regexps, Class type, CaptureGroupTransformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch) { this(name, regexps, (Type) type, transformer, useForSnippets, preferForRegexpMatch); } - public ParameterType(String name, String regexp, Class type, CaptureGroupTransformer transformer, boolean useForSnippets, boolean preferForRegexpMatch) { + public ParameterType(String name, String regexp, Class type, CaptureGroupTransformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch) { this(name, singletonList(regexp), type, transformer, useForSnippets, preferForRegexpMatch); } @@ -112,27 +128,36 @@ public ParameterType(String name, String regexp, Class type, CaptureGroupTran this(name, singletonList(regexp), type, transformer, true, false); } - public ParameterType(String name, List regexps, Type type, Transformer transformer, boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint) { - this(name, regexps, type, new TransformerAdaptor<>(transformer), useForSnippets, preferForRegexpMatch, useRegexpMatchAsStrongTypeHint); + public ParameterType(String name, List regexps, Type type, Transformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint) { + this(name, regexps, type, new TransformerAdaptor<>(transformer), useForSnippets, preferForRegexpMatch, + useRegexpMatchAsStrongTypeHint); } - public ParameterType(String name, List regexps, Type type, Transformer transformer, boolean useForSnippets, boolean preferForRegexpMatch) { + public ParameterType(String name, List regexps, Type type, Transformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch) { this(name, regexps, type, new TransformerAdaptor<>(transformer), useForSnippets, preferForRegexpMatch); } - public ParameterType(String name, List regexps, Class type, Transformer transformer, boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint) { - this(name, regexps, (Type) type, transformer, useForSnippets, preferForRegexpMatch, useRegexpMatchAsStrongTypeHint); + public ParameterType(String name, List regexps, Class type, Transformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint) { + this(name, regexps, (Type) type, transformer, useForSnippets, preferForRegexpMatch, + useRegexpMatchAsStrongTypeHint); } - public ParameterType(String name, List regexps, Class type, Transformer transformer, boolean useForSnippets, boolean preferForRegexpMatch) { + public ParameterType(String name, List regexps, Class type, Transformer transformer, + boolean useForSnippets, boolean preferForRegexpMatch) { this(name, regexps, (Type) type, transformer, useForSnippets, preferForRegexpMatch); } - public ParameterType(String name, String regexp, Class type, Transformer transformer, boolean useForSnippets, boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint) { - this(name, singletonList(regexp), type, transformer, useForSnippets, preferForRegexpMatch, useRegexpMatchAsStrongTypeHint); + public ParameterType(String name, String regexp, Class type, Transformer transformer, boolean useForSnippets, + boolean preferForRegexpMatch, boolean useRegexpMatchAsStrongTypeHint) { + this(name, singletonList(regexp), type, transformer, useForSnippets, preferForRegexpMatch, + useRegexpMatchAsStrongTypeHint); } - public ParameterType(String name, String regexp, Class type, Transformer transformer, boolean useForSnippets, boolean preferForRegexpMatch) { + public ParameterType(String name, String regexp, Class type, Transformer transformer, boolean useForSnippets, + boolean preferForRegexpMatch) { this(name, singletonList(regexp), type, transformer, useForSnippets, preferForRegexpMatch); } @@ -211,7 +236,8 @@ public boolean useRegexpMatchAsStrongTypeHint() { } ParameterType deAnonymize(Type type, Transformer transformer) { - return new ParameterType<>("anonymous", regexps, type, new TransformerAdaptor<>(transformer), useForSnippets, preferForRegexpMatch, useRegexpMatchAsStrongTypeHint, anonymous); + return new ParameterType<>("anonymous", regexps, type, new TransformerAdaptor<>(transformer), useForSnippets, + preferForRegexpMatch, useRegexpMatchAsStrongTypeHint, anonymous); } T transform(List groupValues) { @@ -234,14 +260,18 @@ T transform(List groupValues) { } catch (CucumberExpressionException e) { throw e; } catch (Throwable throwable) { - throw new CucumberExpressionException(String.format("ParameterType {%s} failed to transform %s to %s", name, groupValues, type), throwable); + throw new CucumberExpressionException( + String.format("ParameterType {%s} failed to transform %s to %s", name, groupValues, type), + throwable); } } @Override public int compareTo(ParameterType o) { - if (preferForRegexpMatch() && !o.preferForRegexpMatch()) return -1; - if (o.preferForRegexpMatch() && !preferForRegexpMatch()) return 1; + if (preferForRegexpMatch() && !o.preferForRegexpMatch()) + return -1; + if (o.preferForRegexpMatch() && !preferForRegexpMatch()) + return 1; String name = getName() != null ? getName() : ""; String otherName = o.getName() != null ? o.getName() : ""; return name.compareTo(otherName); @@ -259,7 +289,8 @@ private static final class TransformerAdaptor implements CaptureGroupTransfor private final Transformer transformer; private TransformerAdaptor(Transformer transformer) { - if (transformer == null) throw new NullPointerException("transformer cannot be null"); + if (transformer == null) + throw new NullPointerException("transformer cannot be null"); this.transformer = transformer; } @@ -267,5 +298,7 @@ private TransformerAdaptor(Transformer transformer) { public T transform(String[] args) throws Throwable { return transformer.transform(args[0]); } + } + } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java index 759a5b7db9..6239035a31 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java @@ -75,7 +75,7 @@ public void throws_exception_for_illegal_character_in_parameter_name() { ); final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Illegal character '[' in parameter name {[string]}."))); + assertThat(thrownException.getMessage(), is(equalTo("Illegal character in parameter name {[string]}. Parameter names may not contain '[]()$.|?*+'"))); } @Test diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml index 9c64faaaed..1dd65aa276 100644 --- a/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml @@ -2,4 +2,9 @@ expression: |- {[string]} text: something exception: |- - Illegal character '[' in parameter name {[string]}. + This Cucumber Expression has a problem at column 1: + + {[string]} + ^--------^ + Parameter names may not contain '[]()$.|?*+'. + Did you mean to use a regular expression? From 010578070fccae36186952cc8bbaf7ad1b1593bd Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 22:54:14 +0200 Subject: [PATCH 126/183] Complain --- .../cucumberexpressions/CucumberExpressionException.java | 9 ++++++--- .../cucumber/cucumberexpressions/ExpressionFactory.java | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index be92754c14..089846d858 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -94,6 +94,12 @@ static CucumberExpressionException createInvalidParameterTypeName(String name) { "Illegal character in parameter name {" + name + "}. Parameter names may not contain '[]()$.|?*+'"); } + /** + * Not very clear, but this message has to be language independent + * Other languages have dedicated syntax for writing down regular expressions + * + * @see ExpressionFactory + */ static CucumberExpressionException createInvalidParameterTypeName(Node node, String expression) { return new CucumberExpressionException(message( node.start(), @@ -103,7 +109,6 @@ static CucumberExpressionException createInvalidParameterTypeName(Node node, Str "Did you mean to use a regular expression?")); } - private static String message(int index, String expression, StringBuilder pointer, String problem, String solution) { return thisCucumberExpressionHasAProblemAt(index) + @@ -134,6 +139,4 @@ private static StringBuilder pointAt(int index) { return pointer; } - - } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java index 5d93c4464e..6565bc861f 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java @@ -10,6 +10,9 @@ * Creates a {@link CucumberExpression} or {@link RegularExpression} from a {@link String} * using heuristics. This is particularly useful for languages that don't have a * literal syntax for regular expressions. In Java, a regular expression has to be represented as a String. + * + * A string that starts with `^` and/or ends with `$` is considered a regular expression. + * Everything else is considered a Cucumber expression. */ @API(status = API.Status.STABLE) public final class ExpressionFactory { From a0722ca7cebe47cbfda76e373c0798c5779ae155 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 23:06:27 +0200 Subject: [PATCH 127/183] Improve UndefinedParameterTypeException message --- .../cucumberexpressions/CucumberExpression.java | 3 ++- .../CucumberExpressionException.java | 4 ++-- .../UndefinedParameterTypeException.java | 14 ++++++++++++-- .../cucumberexpressions/ExpressionFactoryTest.java | 7 ------- .../expression/throws-unknown-parameter-type.yaml | 7 ++++++- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index a63eeeb13a..2a91bbb83a 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -16,6 +16,7 @@ import static io.cucumber.cucumberexpressions.CucumberExpressionException.createInvalidParameterTypeName; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInOptional; +import static io.cucumber.cucumberexpressions.UndefinedParameterTypeException.createUndefinedParameterType; import static java.util.stream.Collectors.joining; @API(status = API.Status.STABLE) @@ -92,7 +93,7 @@ private String rewriteParameter(Node node) { String name = assertValidParameterTypeName(node, astNode -> createInvalidParameterTypeName(astNode, source)); ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); if (parameterType == null) { - throw new UndefinedParameterTypeException(name); + throw createUndefinedParameterType(node, source, name); } parameterTypes.add(parameterType); List regexps = parameterType.getRegexps(); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 089846d858..20fd69380c 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -109,7 +109,7 @@ static CucumberExpressionException createInvalidParameterTypeName(Node node, Str "Did you mean to use a regular expression?")); } - private static String message(int index, String expression, StringBuilder pointer, String problem, + static String message(int index, String expression, StringBuilder pointer, String problem, String solution) { return thisCucumberExpressionHasAProblemAt(index) + "\n" + @@ -119,7 +119,7 @@ private static String message(int index, String expression, StringBuilder pointe solution; } - private static StringBuilder pointAt(Located node) { + static StringBuilder pointAt(Located node) { StringBuilder pointer = pointAt(node.start()); if (node.start() + 1 < node.end()) { for (int i = node.start() + 1; i < node.end() - 1; i++) { diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/UndefinedParameterTypeException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/UndefinedParameterTypeException.java index f3cf7666d1..b2b9efede7 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/UndefinedParameterTypeException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/UndefinedParameterTypeException.java @@ -1,17 +1,27 @@ package io.cucumber.cucumberexpressions; +import io.cucumber.cucumberexpressions.Ast.Node; import org.apiguardian.api.API; @API(status = API.Status.STABLE) public final class UndefinedParameterTypeException extends CucumberExpressionException { private final String undefinedParameterTypeName; - UndefinedParameterTypeException(String undefinedParameterTypeName) { - super(String.format("Undefined parameter type {%s}. Please register a ParameterType for {%s}.", undefinedParameterTypeName, undefinedParameterTypeName)); + UndefinedParameterTypeException(String message, String undefinedParameterTypeName) { + super(message); this.undefinedParameterTypeName = undefinedParameterTypeName; } public String getUndefinedParameterTypeName() { return undefinedParameterTypeName; } + + static CucumberExpressionException createUndefinedParameterType(Node node, String expression, String undefinedParameterTypeName) { + return new UndefinedParameterTypeException(message( + node.start(), + expression, + pointAt(node), + "Undefined parameter type '" +undefinedParameterTypeName+ "'", + "Please register a ParameterType for '"+undefinedParameterTypeName+"'"), undefinedParameterTypeName); + } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionFactoryTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionFactoryTest.java index 7eec010bd9..ee0826651d 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionFactoryTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/ExpressionFactoryTest.java @@ -72,13 +72,6 @@ public void explains_cukexp_regexp_mix() { assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("You cannot use anchors (^ or $) in Cucumber Expressions. Please remove them from ^the seller has {int} strike(s)$"))); } - @Test - public void explains_undefined_parameter_types() { - final Executable testMethod = () -> createExpression("{x}"); - final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat("Unexpected message", thrownException.getMessage(), is(equalTo("Undefined parameter type {x}. Please register a ParameterType for {x}."))); - } - private void assertRegularExpression(String expressionString) { assertRegularExpression(expressionString, expressionString); } diff --git a/cucumber-expressions/java/testdata/expression/throws-unknown-parameter-type.yaml b/cucumber-expressions/java/testdata/expression/throws-unknown-parameter-type.yaml index 3061f3e1bb..384e3a48c3 100644 --- a/cucumber-expressions/java/testdata/expression/throws-unknown-parameter-type.yaml +++ b/cucumber-expressions/java/testdata/expression/throws-unknown-parameter-type.yaml @@ -2,4 +2,9 @@ expression: |- {unknown} text: something exception: |- - Undefined parameter type {unknown}. Please register a ParameterType for {unknown}. + This Cucumber Expression has a problem at column 1: + + {unknown} + ^-------^ + Undefined parameter type 'unknown'. + Please register a ParameterType for 'unknown' From 41586cbf53018d510c723cc682d378796b6ed934 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Sep 2020 23:59:32 +0200 Subject: [PATCH 128/183] Attribution --- cucumber-expressions/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 1bea12ffb9..9f20f634da 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -59,3 +59,6 @@ other BDD tools, such as [Turnip](https://github.com/jnicklas/turnip), Big thanks to Jonas Nicklas, Konstantin Kudryashov and Jens Engel for implementing those libraries. +The [Tiny-Compiler-Parser tutorial](https://blog.klipse.tech/javascript/2017/02/08/tiny-compiler-parser.html) +by [Yehonathan Sharvit](https://github.com/viebel) inspired the design of the +Cucumber expression parser. From 9650cab280e73623f4ddbdcaa3b90d1309be2f59 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2020 12:14:22 +0200 Subject: [PATCH 129/183] Make acceptance tests work --- .../go/cucumber_expression_parser_test.go | 8 +- .../go/cucumber_expression_test.go | 339 +++--------------- .../go/cucumber_expression_tokenizer_test.go | 27 +- .../CucumberExpressionParserTest.java | 4 +- .../CucumberExpressionTest.java | 4 +- .../CucumberExpressionTokenizerTest.java | 4 +- 6 files changed, 80 insertions(+), 306 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index 97939951c5..5db2ef6b63 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -10,13 +10,13 @@ import ( ) func TestCucumberExpressionParser(t *testing.T) { - var assertAst = func(t *testing.T, expression string, expected node) { + var assertAst = func(t *testing.T, expected node, expression string) { ast, err := parse(expression) require.NoError(t, err) require.Equal(t, expected, ast) require.Equal(t, expected, ast) } - var assertThrows = func(t *testing.T, expression string, expected string) { + var assertThrows = func(t *testing.T, expected string, expression string) { _, err := parse(expression) require.Error(t, err) require.Equal(t, expected, err.Error()) @@ -38,9 +38,9 @@ func TestCucumberExpressionParser(t *testing.T) { var node node err = json.Unmarshal([]byte(expectation.Expected), &node) require.NoError(t, err) - assertAst(t, expectation.Expression, node) + assertAst(t, node, expectation.Expression) } else { - assertThrows(t, expectation.Expression, expectation.Exception) + assertThrows(t, expectation.Exception, expectation.Expression) } }) } diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index dd9c9a9c38..277e887281 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -1,14 +1,66 @@ package cucumberexpressions import ( + "encoding/json" "fmt" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + "io/ioutil" "reflect" "regexp" "testing" ) func TestCucumberExpression(t *testing.T) { + + t.Run("acceptance tests pass", func(t *testing.T) { + + assertMatches := func(t *testing.T, expected string, expr string, text string) { + parameterTypeRegistry := NewParameterTypeRegistry() + expression, err := NewCucumberExpression(expr, parameterTypeRegistry) + require.NoError(t, err) + args, err := expression.Match(text) + require.NoError(t, err) + values, err := json.Marshal(argumentValues(args)) + require.NoError(t, err) + require.Equal(t, expected, string(values)) + } + + assertThrows := func(t *testing.T, expected string, expr string, text string) { + parameterTypeRegistry := NewParameterTypeRegistry() + expression, err := NewCucumberExpression(expr, parameterTypeRegistry) + + if err != nil { + require.Error(t, err) + require.Equal(t, expected, err.Error()) + } else { + _, err = expression.Match(text) + require.Error(t, err) + require.Equal(t, expected, err.Error()) + } + } + + directory := "../java/testdata/expression/" + files, err := ioutil.ReadDir(directory) + require.NoError(t, err) + + for _, file := range files { + contents, err := ioutil.ReadFile(directory + file.Name()) + require.NoError(t, err) + t.Run(fmt.Sprintf("%s", file.Name()), func(t *testing.T) { + var expectation expectation + err = yaml.Unmarshal(contents, &expectation) + require.NoError(t, err) + + if expectation.Exception == "" { + assertMatches(t, expectation.Expected, expectation.Expression, expectation.Text) + } else { + assertThrows(t, expectation.Expected, expectation.Expression, expectation.Text) + } + }) + } + }) + t.Run("documents expression generation", func(t *testing.T) { parameterTypeRegistry := NewParameterTypeRegistry() @@ -22,261 +74,6 @@ func TestCucumberExpression(t *testing.T) { /// [capture-match-arguments] }) - t.Run("matches word", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {word} mice", "three blind mice"), - []interface{}{"blind"}, - ) - }) - - t.Run("matches alternation", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "mice/rats and rats\\/mice", "rats and rats/mice"), - []interface{}{}, - ) - }) - - t.Run("matches optional before alternation", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three (brown )mice/rats", "three rats"), - []interface{}{}, - ) - require.Equal( - t, - MatchCucumberExpression(t, "three (brown )mice/rats", "three brown mice"), - []interface{}{}, - ) - }) - - t.Run("matches optional in alternation", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "{int} rat(s)/mice/mouse", "3 rats"), - []interface{}{3}, - ) - require.Equal( - t, - MatchCucumberExpression(t, "{int} rat(s)/mice/mouse", "2 mice"), - []interface{}{2}, - ) - require.Equal( - t, - MatchCucumberExpression(t, "{int} rat(s)/mice/mouse", "1 mouse"), - []interface{}{1}, - ) - - }) - - t.Run("matches optional before alternation with regex characters", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "I wait {int} second(s)./second(s)?", "I wait 2 seconds?"), - []interface{}{2}, - ) - require.Equal( - t, - MatchCucumberExpression(t, "I wait {int} second(s)./second(s)?", "I wait 1 second?"), - []interface{}{1}, - ) - - }) - - t.Run("matches alternation in optional as text", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three( brown/black) mice", "three brown/black mice"), - []interface{}{}, - ) - }) - - t.Run("does not allow empty optional", func(t *testing.T) { - parameterTypeRegistry := NewParameterTypeRegistry() - _, err := NewCucumberExpression("three () mice", parameterTypeRegistry) - require.Error(t, err) - require.Equal(t, "Optional may not be empty: three () mice", err.Error()) - }) - - t.Run("does not allow alternation with empty alternatives", func(t *testing.T) { - parameterTypeRegistry := NewParameterTypeRegistry() - _, err := NewCucumberExpression("three brown//black mice", parameterTypeRegistry) - require.Error(t, err) - require.Equal(t, "Alternative may not be empty: three brown//black mice", err.Error()) - }) - - t.Run("does not allow optional adjacent to alternation", func(t *testing.T) { - parameterTypeRegistry := NewParameterTypeRegistry() - _, err := NewCucumberExpression("three (brown)/black mice", parameterTypeRegistry) - require.Error(t, err) - require.Equal(t, "Alternative may not exclusively contain optionals: three (brown)/black mice", err.Error()) - }) - - t.Run("matches double quoted string", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} mice", `three "blind" mice`), - []interface{}{"blind"}, - ) - }) - - t.Run("matches multiple double quoted strings", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} and {string} mice", `three "blind" and "crippled" mice`), - []interface{}{"blind", "crippled"}, - ) - }) - - t.Run("matches single quoted string", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} mice", `three 'blind' mice`), - []interface{}{"blind"}, - ) - }) - - t.Run("matches multiple single quoted strings", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} and {string} mice", `three 'blind' and 'crippled' mice`), - []interface{}{"blind", "crippled"}, - ) - }) - - t.Run("does not match misquoted string", func(t *testing.T) { - require.Nil( - t, - MatchCucumberExpression(t, "three {string} mice", `three "blind' mice`), - ) - }) - - t.Run("matches single quoted strings with double quotes", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} mice", `three '"blind"' mice`), - []interface{}{`"blind"`}, - ) - }) - - t.Run("matches double quoted strings with single quotes", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} mice", `three "'blind'" mice`), - []interface{}{`'blind'`}, - ) - }) - - t.Run("matches double quoted string with escaped double quote", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} mice", `three "bl\"nd" mice`), - []interface{}{`bl\"nd`}, - ) - }) - - t.Run("matches single quoted string with escaped single quote", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} mice", `three 'bl\'nd' mice`), - []interface{}{`bl\'nd`}, - ) - }) - - t.Run("matches single quoted empty string as empty string", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} mice", `three '' mice`), - []interface{}{""}, - ) - }) - - t.Run("matches double quoted empty string as empty string", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} mice", `three "" mice`), - []interface{}{""}, - ) - }) - - t.Run("matches single quoted empty string as empty string along with other strings", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} and {string} mice", `three '' and 'handsome' mice`), - []interface{}{"", "handsome"}, - ) - }) - - t.Run("matches double quoted empty string as empty string along with other strings", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three {string} and {string} mice", `three "" and "handsome" mice`), - []interface{}{"", "handsome"}, - ) - }) - - t.Run("matches escaped parenthesis", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three \\(exceptionally) \\{string} mice", `three (exceptionally) {string} mice`), - []interface{}{}, - ) - require.Equal( - t, - MatchCucumberExpression(t, "three \\((exceptionally)) \\{{string}} mice", `three (exceptionally) {"blind"} mice`), - []interface{}{"blind"}, - ) - require.Equal( - t, - MatchCucumberExpression(t, "three ((exceptionally\\)) {{string\\}} mice", `three (exceptionally) "blind" mice`), - []interface{}{"\"blind\""}, - ) - }) - - t.Run("matches doubly escaped parenthesis", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "three \\\\(exceptionally) \\\\{string} mice", `three \exceptionally \"blind" mice`), - []interface{}{"blind"}, - ) - }) - - t.Run("matches escaped slash", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "12\\/2020", `12/2020`), - []interface{}{}, - ) - }) - t.Run("matches doubly escaped slash", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "12\\\\/2020", `12\`), - []interface{}{}, - ) - require.Equal( - t, - MatchCucumberExpression(t, "12\\\\/2020", `2020`), - []interface{}{}, - ) - }) - - t.Run("matches int", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "{int}", "22"), - []interface{}{22}, - ) - }) - - t.Run("doesn't match float as int", func(t *testing.T) { - require.Nil( - t, - MatchCucumberExpression(t, "{int}", "1.22"), - ) - }) - t.Run("matches float", func(t *testing.T) { require.Equal(t, MatchCucumberExpression(t, "{float}", ""), []interface{}(nil)) require.Equal(t, MatchCucumberExpression(t, "{float}", "."), []interface{}(nil)) @@ -338,34 +135,6 @@ func TestCucumberExpression(t *testing.T) { ) }) - t.Run("does not allow parameter type with left bracket", func(t *testing.T) { - parameterTypeRegistry := NewParameterTypeRegistry() - _, err := NewCucumberExpression("{[string]}", parameterTypeRegistry) - require.Error(t, err) - require.Equal(t, err.Error(), "illegal character '[' in parameter name {[string]}") - }) - - t.Run("does not allow optional parameter types", func(t *testing.T) { - parameterTypeRegistry := NewParameterTypeRegistry() - _, err := NewCucumberExpression("({int})", parameterTypeRegistry) - require.Error(t, err) - require.Equal(t, "Parameter types cannot be optional: ({int})", err.Error()) - }) - - t.Run("allows escaped optional parameters", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression(t, "\\({int})", `(3)`), - []interface{}{3}, - ) - }) - - t.Run("returns UndefinedParameterTypeExpression for unknown parameter", func(t *testing.T) { - parameterTypeRegistry := NewParameterTypeRegistry() - _, err := NewCucumberExpression("{unknown}", parameterTypeRegistry) - require.Error(t, err) - }) - t.Run("exposes source", func(t *testing.T) { expr := "I have {int} cuke(s)" parameterTypeRegistry := NewParameterTypeRegistry() @@ -480,6 +249,10 @@ func MatchCucumberExpression(t *testing.T, expr string, text string, typeHints . require.NoError(t, err) args, err := expression.Match(text, typeHints...) require.NoError(t, err) + return argumentValues(args) +} + +func argumentValues(args []*Argument) []interface{} { if args == nil { return nil } diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index 9921095e6a..13b3cd6210 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -11,24 +11,13 @@ import ( type expectation struct { Expression string `yaml:"expression"` + Text string `yaml:"text"` Expected string `yaml:"expected"` Exception string `yaml:"exception"` } func TestCucumberExpressionTokenizer(t *testing.T) { - var assertContains = func(t *testing.T, expression string, expected []token) { - tokens, err := tokenize(expression) - require.NoError(t, err) - require.Equal(t, expected, tokens) - } - - var assertThrows = func(t *testing.T, expression string, expected string) { - _, err := tokenize(expression) - require.Error(t, err) - require.Equal(t, expected, err.Error()) - } - directory := "../java/testdata/tokens/" files, err := ioutil.ReadDir(directory) require.NoError(t, err) @@ -45,10 +34,22 @@ func TestCucumberExpressionTokenizer(t *testing.T) { var token []token err = json.Unmarshal([]byte(expectation.Expected), &token) require.NoError(t, err) - assertContains(t, expectation.Expression, token) + assertTokenizes(t, token, expectation.Expression) } else { assertThrows(t, expectation.Expression, expectation.Exception) } }) } } + +func assertTokenizes(t *testing.T, expected []token, expression string) { + tokens, err := tokenize(expression) + require.NoError(t, err) + require.Equal(t, expected, tokens) +} + +func assertThrows(t *testing.T, expected string, expression string) { + _, err := tokenize(expression) + require.Error(t, err) + require.Equal(t, expected, err.Error()) +} diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java index 32743dbb9b..63d1c3e18d 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionParserTest.java @@ -21,7 +21,7 @@ class CucumberExpressionParserTest { private final CucumberExpressionParser parser = new CucumberExpressionParser(); - private static List test() throws IOException { + private static List acceptance_tests_pass() throws IOException { List paths = new ArrayList<>(); newDirectoryStream(Paths.get("testdata", "ast")).forEach(paths::add); paths.sort(Comparator.naturalOrder()); @@ -30,7 +30,7 @@ private static List test() throws IOException { @ParameterizedTest @MethodSource - void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { + void acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { if (expectation.getException() == null) { Node node = parser.parse(expectation.getExpression()); assertThat(node.toString(), is(expectation.getExpected())); diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 0b629ad793..401fb1bd1f 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -31,7 +31,7 @@ class CucumberExpressionTest { private final ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); - private static List test() throws IOException { + private static List acceptance_tests_pass() throws IOException { List paths = new ArrayList<>(); newDirectoryStream(Paths.get("testdata", "expression")).forEach(paths::add); paths.sort(Comparator.naturalOrder()); @@ -40,7 +40,7 @@ private static List test() throws IOException { @ParameterizedTest @MethodSource - void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { + void acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { if (expectation.getException() == null) { CucumberExpression expression = new CucumberExpression(expectation.getExpression(), parameterTypeRegistry); List> match = expression.match(expectation.getText()); diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java index 84b3a15d86..aaf5084672 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizerTest.java @@ -22,7 +22,7 @@ class CucumberExpressionTokenizerTest { private final CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer(); - private static List test() throws IOException { + private static List acceptance_tests_pass() throws IOException { List paths = new ArrayList<>(); newDirectoryStream(Paths.get("testdata", "tokens")).forEach(paths::add); paths.sort(Comparator.naturalOrder()); @@ -31,7 +31,7 @@ private static List test() throws IOException { @ParameterizedTest @MethodSource - void test(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { + void acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { if (expectation.getException() == null) { String tokens = tokenizer .tokenize(expectation.getExpression()) From 059c3732b4ada2df3df9201334b7fd6690b78f60 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2020 14:58:41 +0200 Subject: [PATCH 130/183] Nearly all tests pass --- .../go/cucumber_expression.go | 57 ++++++------ .../go/cucumber_expression_test.go | 2 +- .../go/cucumber_expression_tokenizer_test.go | 2 +- cucumber-expressions/go/errors.go | 93 +++++++++++++++++-- cucumber-expressions/go/parameter_type.go | 12 +-- .../CucumberExpression.java | 22 ++--- .../CucumberExpressionException.java | 16 ++-- 7 files changed, 140 insertions(+), 64 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 376fd05312..86e59c1547 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -7,13 +7,6 @@ import ( "strings" ) -const alternativesMayNotBeEmpty = "Alternative may not be empty: %s" -const parameterTypesCanNotBeAlternative = "Parameter types cannot be alternative: %s" -const parameterTypesCanNotBeOptional = "Parameter types cannot be optional: %s" -const alternativeMayNotExclusivelyContainOptionals = "Alternative may not exclusively contain optionals: %s" -const couldNotRewrite = "Could not rewrite %s" -const optionalMayNotBeEmpty = "Optional may not be empty: %s" - var escapeRegexp = regexp.MustCompile(`([\\^\[({$.|?*+})\]])`) type CucumberExpression struct { @@ -54,7 +47,8 @@ func (c *CucumberExpression) rewriteNodeToRegex(node node) (string, error) { case expressionNode: return c.rewriteExpression(node) default: - return "", NewCucumberExpressionError(fmt.Sprintf(couldNotRewrite, c.source)) + // Can't happen as long as the switch case is exhaustive + return "", NewCucumberExpressionError(fmt.Sprintf("Could not rewrite %s", c.source)) } } @@ -63,28 +57,36 @@ func (c *CucumberExpression) processEscapes(expression string) string { } func (c *CucumberExpression) rewriteOptional(node node) (string, error) { - err := c.assertNoParameters(node, parameterTypesCanNotBeOptional) + err := c.assertNoParameters(node, c.createParameterIsNotAllowedInOptional()) if err != nil { return "", err } - err = c.assertNotEmpty(node, optionalMayNotBeEmpty) + err = c.assertNotEmpty(node, c.createOptionalMayNotBeEmpty()) if err != nil { return "", err } return c.rewriteNodesToRegex(node.Nodes, "", "(?:", ")?") } +func (c *CucumberExpression) createParameterIsNotAllowedInOptional() func(node) error { + return func(node node) error { + return createParameterIsNotAllowedInOptional(node, c.source) + } +} + +func (c *CucumberExpression) createOptionalMayNotBeEmpty() func(node) error { + return func(node node) error { + return createOptionalMayNotBeEmpty(node, c.source) + } +} + func (c *CucumberExpression) rewriteAlternation(node node) (string, error) { // Make sure the alternative parts aren't empty and don't contain parameter types for _, alternative := range node.Nodes { if len(alternative.Nodes) == 0 { - return "", NewCucumberExpressionError(fmt.Sprintf(alternativesMayNotBeEmpty, c.source)) + return "", createAlternativeMayNotBeEmpty(alternative, c.source) } - err := c.assertNoParameters(alternative, parameterTypesCanNotBeAlternative) - if err != nil { - return "", err - } - err = c.assertNotEmpty(alternative, alternativeMayNotExclusivelyContainOptionals) + err := c.assertNotEmpty(alternative, c.createAlternativeMayNotExclusivelyContainOptionals()) if err != nil { return "", err } @@ -92,6 +94,12 @@ func (c *CucumberExpression) rewriteAlternation(node node) (string, error) { return c.rewriteNodesToRegex(node.Nodes, "|", "(?:", ")") } +func (c *CucumberExpression) createAlternativeMayNotExclusivelyContainOptionals() func(node) error { + return func(node node) error { + return createAlternativeMayNotExclusivelyContainOptionals(node, c.source) + } +} + func (c *CucumberExpression) rewriteAlternative(node node) (string, error) { return c.rewriteNodesToRegex(node.Nodes, "", "", "") } @@ -109,16 +117,13 @@ func (c *CucumberExpression) rewriteParameter(node node) (string, error) { return fmt.Sprintf("(%s)", strings.Join(captureGroups, "|")) } - typeName := node.text() - err := CheckParameterTypeName(typeName) - if err != nil { - return "", err + if isValidParameterTypeName(typeName) { + return "", createInvalidParameterTypeNameInNode(node, c.source) } parameterType := c.parameterTypeRegistry.LookupByTypeName(typeName) if parameterType == nil { - err = NewUndefinedParameterTypeError(typeName) - return "", err + return "", createUndefinedParameterType(node, c.source, typeName) } c.parameterTypes = append(c.parameterTypes, parameterType) return buildCaptureRegexp(parameterType.regexps), nil @@ -145,19 +150,19 @@ func (c *CucumberExpression) rewriteNodesToRegex(nodes []node, delimiter string, return builder.String(), nil } -func (c *CucumberExpression) assertNotEmpty(node node, message string) error { +func (c *CucumberExpression) assertNotEmpty(node node, createNodeWasNotEmptyError func(node) error) error { for _, node := range node.Nodes { if node.NodeType == textNode { return nil } } - return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) + return createNodeWasNotEmptyError(node) } -func (c *CucumberExpression) assertNoParameters(node node, message string) error { +func (c *CucumberExpression) assertNoParameters(node node, createParameterIsNotAllowedInOptionalError func(node) error) error { for _, node := range node.Nodes { if node.NodeType == parameterNode { - return NewCucumberExpressionError(fmt.Sprintf(message, c.source)) + return createParameterIsNotAllowedInOptionalError(node) } } return nil diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 277e887281..5991c404e3 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -55,7 +55,7 @@ func TestCucumberExpression(t *testing.T) { if expectation.Exception == "" { assertMatches(t, expectation.Expected, expectation.Expression, expectation.Text) } else { - assertThrows(t, expectation.Expected, expectation.Expression, expectation.Text) + assertThrows(t, expectation.Exception, expectation.Expression, expectation.Text) } }) } diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index 13b3cd6210..ef984de62e 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -36,7 +36,7 @@ func TestCucumberExpressionTokenizer(t *testing.T) { require.NoError(t, err) assertTokenizes(t, token, expectation.Expression) } else { - assertThrows(t, expectation.Expression, expectation.Exception) + assertThrows(t, expectation.Exception, expectation.Expression) } }) } diff --git a/cucumber-expressions/go/errors.go b/cucumber-expressions/go/errors.go index 8363c9f722..8ebda02e6a 100644 --- a/cucumber-expressions/go/errors.go +++ b/cucumber-expressions/go/errors.go @@ -39,6 +39,44 @@ func createTheEndOfLineCanNotBeEscaped(expression string) error { )) } +func createOptionalMayNotBeEmpty(node node, expression string) error { + return NewCucumberExpressionError(message( + node.Start, + expression, + pointAtNode(node), + "An optional must contain some text", + "If you did not mean to use an optional you can use '\\(' to escape the the '('", + )) +} +func createParameterIsNotAllowedInOptional(node node, expression string) error { + return NewCucumberExpressionError(message( + node.Start, + expression, + pointAtNode(node), + "An optional may not contain a parameter type", + "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'", + )) +} + +func createAlternativeMayNotBeEmpty(node node, expression string) error { + return NewCucumberExpressionError(message( + node.Start, + expression, + pointAtNode(node), + "Alternative may not be empty", + "If you did not mean to use an alternative you can use '\\/' to escape the the '/'", + )) +} +func createAlternativeMayNotExclusivelyContainOptionals(node node, expression string) error { + return NewCucumberExpressionError(message( + node.Start, + expression, + pointAtNode(node), + "An alternative may not exclusively contain optionals", + "If you did not mean to use an optional you can use '\\(' to escape the the '('", + )) +} + func createCantEscaped(expression string, index int) error { return NewCucumberExpressionError(message( index, @@ -49,31 +87,59 @@ func createCantEscaped(expression string, index int) error { )) } -func pointAt(index int) strings.Builder { +func createInvalidParameterTypeName(typeName string) error { + return NewCucumberExpressionError("Illegal character in parameter name {" + typeName + "}. Parameter names may not contain '[]()$.|?*+'") +} + +// Not very clear, but this message has to be language independent +func createInvalidParameterTypeNameInNode(node node, expression string) error { + return NewCucumberExpressionError(message( + node.Start, + expression, + pointAtNode(node), + "Parameter names may not contain '[]()$.|?*+'", + "Did you mean to use a regular expression?", + )) +} + +func pointAt(index int) string { pointer := strings.Builder{} for i := 0; i < index; i++ { pointer.WriteString(" ") } pointer.WriteString("^") - return pointer + return pointer.String() } -func pointAtToken(node token) strings.Builder { - pointer := pointAt(node.Start) +func pointAtToken(node token) string { + pointer := strings.Builder{} + pointer.WriteString(pointAt(node.Start)) if node.Start+1 < node.End { for i := node.Start + 1; i < node.End-1; i++ { pointer.WriteString("-") } pointer.WriteString("^") } - return pointer + return pointer.String() } -func message(index int, expression string, pointer strings.Builder, problem string, solution string) string { +func pointAtNode(node node) string { + pointer := strings.Builder{} + pointer.WriteString(pointAt(node.Start)) + if node.Start+1 < node.End { + for i := node.Start + 1; i < node.End-1; i++ { + pointer.WriteString("-") + } + pointer.WriteString("^") + } + return pointer.String() +} + +func message(index int, expression string, pointer string, problem string, solution string) string { return thisCucumberExpressionHasAProblemAt(index) + "\n" + expression + "\n" + - pointer.String() + "\n" + + pointer + "\n" + problem + ".\n" + solution } @@ -125,10 +191,19 @@ type UndefinedParameterTypeError struct { s string } -func NewUndefinedParameterTypeError(typeName string) error { - return &UndefinedParameterTypeError{s: fmt.Sprintf("Undefined parameter type {%s}", typeName)} +func NewUndefinedParameterTypeError(message string) error { + return &UndefinedParameterTypeError{s: message} } func (e *UndefinedParameterTypeError) Error() string { return e.s } + +func createUndefinedParameterType(node node, expression string, undefinedParameterTypeName string) error { + return NewUndefinedParameterTypeError(message( + node.Start, + expression, + pointAtNode(node), + "Undefined parameter type '"+undefinedParameterTypeName+"'", + "Please register a ParameterType for '"+undefinedParameterTypeName+"'")) +} diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go index 66b4e14130..ee00a38cbc 100644 --- a/cucumber-expressions/go/parameter_type.go +++ b/cucumber-expressions/go/parameter_type.go @@ -2,13 +2,11 @@ package cucumberexpressions import ( "errors" - "fmt" "reflect" "regexp" ) var HAS_FLAG_REGEXP = regexp.MustCompile(`\(\?[imsU-]+(:.*)?\)`) -var UNESCAPE_REGEXP = regexp.MustCompile(`(\\([\[$.|?*+\]]))`) var ILLEGAL_PARAMETER_NAME_REGEXP = regexp.MustCompile(`([\[\]()$.|?*+])`) type ParameterType struct { @@ -22,14 +20,16 @@ type ParameterType struct { } func CheckParameterTypeName(typeName string) error { - unescapedTypeName := UNESCAPE_REGEXP.ReplaceAllString(typeName, "$2") - if ILLEGAL_PARAMETER_NAME_REGEXP.MatchString(typeName) { - c := ILLEGAL_PARAMETER_NAME_REGEXP.FindStringSubmatch(typeName)[0] - return fmt.Errorf("illegal character '%s' in parameter name {%s}", c, unescapedTypeName) + if isValidParameterTypeName(typeName) { + return createInvalidParameterTypeName(typeName) } return nil } +func isValidParameterTypeName(typeName string) bool { + return ILLEGAL_PARAMETER_NAME_REGEXP.MatchString(typeName) +} + func NewParameterType(name string, regexps []*regexp.Regexp, type1 string, transform func(...*string) interface{}, useForSnippets bool, preferForRegexpMatch bool, useRegexpMatchAsStrongTypeHint bool) (*ParameterType, error) { if transform == nil { transform = func(s ...*string) interface{} { diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 2a91bbb83a..77547e2dd5 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -11,11 +11,12 @@ import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE; -import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeIsEmpty; -import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayExclusivelyContainOptionals; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayNotBeEmpty; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayNotExclusivelyContainOptionals; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createInvalidParameterTypeName; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInOptional; +import static io.cucumber.cucumberexpressions.ParameterType.isValidParameterTypeName; import static io.cucumber.cucumberexpressions.UndefinedParameterTypeException.createUndefinedParameterType; import static java.util.stream.Collectors.joining; @@ -73,9 +74,9 @@ private String rewriteAlternation(Node node) { // Make sure the alternative parts aren't empty and don't contain parameter types for (Node alternative : node.nodes()) { if (alternative.nodes().isEmpty()) { - throw createAlternativeIsEmpty(alternative, source); + throw createAlternativeMayNotBeEmpty(alternative, source); } - assertNotEmpty(alternative, astNode -> createAlternativeMayExclusivelyContainOptionals(astNode, source)); + assertNotEmpty(alternative, astNode -> createAlternativeMayNotExclusivelyContainOptionals(astNode, source)); } return node.nodes() .stream() @@ -90,7 +91,10 @@ private String rewriteAlternative(Node node) { } private String rewriteParameter(Node node) { - String name = assertValidParameterTypeName(node, astNode -> createInvalidParameterTypeName(astNode, source)); + String name = node.text(); + if (!isValidParameterTypeName(name)) { + throw createInvalidParameterTypeName(node ,source); + } ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); if (parameterType == null) { throw createUndefinedParameterType(node, source, name); @@ -111,14 +115,6 @@ private String rewriteExpression(Node node) { .collect(joining("", "^", "$")); } - private String assertValidParameterTypeName(Node node, Function createParameterTypeWasNotValidException) { - String name = node.text(); - if (!ParameterType.isValidParameterTypeName(name)) { - throw createParameterTypeWasNotValidException.apply(node); - } - return name; - } - private void assertNotEmpty(Node node, Function createNodeWasNotEmptyException) { node.nodes() diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index 20fd69380c..d6961b2fcf 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -39,7 +39,7 @@ static CucumberExpressionException createTheEndOfLineCanNotBeEscaped(String expr )); } - static CucumberExpressionException createAlternativeIsEmpty(Node node, String expression) { + static CucumberExpressionException createAlternativeMayNotBeEmpty(Node node, String expression) { return new CucumberExpressionException(message( node.start(), expression, @@ -66,7 +66,7 @@ static CucumberExpressionException createOptionalMayNotBeEmpty(Node node, String "If you did not mean to use an optional you can use '\\(' to escape the the '('")); } - static CucumberExpressionException createAlternativeMayExclusivelyContainOptionals(Node node, + static CucumberExpressionException createAlternativeMayNotExclusivelyContainOptionals(Node node, String expression) { return new CucumberExpressionException(message( node.start(), @@ -109,7 +109,7 @@ static CucumberExpressionException createInvalidParameterTypeName(Node node, Str "Did you mean to use a regular expression?")); } - static String message(int index, String expression, StringBuilder pointer, String problem, + static String message(int index, String expression, String pointer, String problem, String solution) { return thisCucumberExpressionHasAProblemAt(index) + "\n" + @@ -119,24 +119,24 @@ static String message(int index, String expression, StringBuilder pointer, Strin solution; } - static StringBuilder pointAt(Located node) { - StringBuilder pointer = pointAt(node.start()); + static String pointAt(Located node) { + StringBuilder pointer = new StringBuilder(pointAt(node.start())); if (node.start() + 1 < node.end()) { for (int i = node.start() + 1; i < node.end() - 1; i++) { pointer.append("-"); } pointer.append("^"); } - return pointer; + return pointer.toString(); } - private static StringBuilder pointAt(int index) { + private static String pointAt(int index) { StringBuilder pointer = new StringBuilder(); for (int i = 0; i < index; i++) { pointer.append(" "); } pointer.append("^"); - return pointer; + return pointer.toString(); } } From fa0037d7efa6666432e4bd6d12bab5448718f095 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 18 Sep 2020 15:55:39 +0200 Subject: [PATCH 131/183] Fix escaping of string parameter types in go --- .../go/cucumber_expression_test.go | 10 +++++++--- .../go/parameter_type_registry.go | 18 ++++++++++++------ .../CucumberExpressionTest.java | 10 +++++++++- ...ouble-quoted-string-with-single-quotes.yaml | 2 +- ...uoted-string-with-escaped-single-quote.yaml | 2 +- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 5991c404e3..6245bbfe20 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "reflect" "regexp" + "strings" "testing" ) @@ -21,9 +22,13 @@ func TestCucumberExpression(t *testing.T) { require.NoError(t, err) args, err := expression.Match(text) require.NoError(t, err) - values, err := json.Marshal(argumentValues(args)) + + values := strings.Builder{} + encoder := json.NewEncoder(&values) + encoder.SetEscapeHTML(false) + err = encoder.Encode(argumentValues(args)) require.NoError(t, err) - require.Equal(t, expected, string(values)) + require.Equal(t, expected, strings.TrimSuffix(values.String(), "\n")) } assertThrows := func(t *testing.T, expected string, expr string, text string) { @@ -51,7 +56,6 @@ func TestCucumberExpression(t *testing.T) { var expectation expectation err = yaml.Unmarshal(contents, &expectation) require.NoError(t, err) - if expectation.Exception == "" { assertMatches(t, expectation.Expected, expectation.Expression, expectation.Text) } else { diff --git a/cucumber-expressions/go/parameter_type_registry.go b/cucumber-expressions/go/parameter_type_registry.go index 155d60e16a..0371575cfb 100644 --- a/cucumber-expressions/go/parameter_type_registry.go +++ b/cucumber-expressions/go/parameter_type_registry.go @@ -5,6 +5,7 @@ import ( "reflect" "regexp" "sort" + "strings" ) var INTEGER_REGEXPS = []*regexp.Regexp{ @@ -98,14 +99,19 @@ func NewParameterTypeRegistry() *ParameterTypeRegistry { STRING_REGEXPS, "string", func(args ...*string) interface{} { - if args[0] == nil && args[1] != nil { - i, err := transformer.Transform(*args[1], reflect.String) - if err != nil { - panic(err) + matched := func(args []*string) string { + var value string + if args[0] == nil && args[1] != nil { + value = *args[1] + } else { + value = *args[0] } - return i + return value } - i, err := transformer.Transform(*args[0], reflect.String) + value := matched(args) + value = strings.ReplaceAll(value, "\\\"", "\"") + value = strings.ReplaceAll(value, "\\'", "'") + i, err := transformer.Transform(value, reflect.String) if err != nil { panic(err) } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 401fb1bd1f..6857d3724d 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -1,6 +1,9 @@ package io.cucumber.cucumberexpressions; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.ExclusionStrategy; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -47,7 +50,12 @@ void acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) Expect List values = match == null ? null : match.stream() .map(Argument::getValue) .collect(Collectors.toList()); - assertEquals(expectation.getExpected(), new Gson().toJson(values)); + + Gson gson = new GsonBuilder() + .disableHtmlEscaping() + .create(); + + assertEquals(expectation.getExpected(), gson.toJson(values)); } else { Executable executable = () -> { String expr = expectation.getExpression(); diff --git a/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml b/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml index d4467ed484..25fcf304c1 100644 --- a/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml +++ b/cucumber-expressions/java/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml @@ -1,4 +1,4 @@ expression: three {string} mice text: three "'blind'" mice expected: |- - ["\u0027blind\u0027"] + ["'blind'"] diff --git a/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml b/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml index 0b7a917c8f..4c2b0055b9 100644 --- a/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml +++ b/cucumber-expressions/java/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml @@ -1,4 +1,4 @@ expression: three {string} mice text: three 'bl\'nd' mice expected: |- - ["bl\u0027nd"] + ["bl'nd"] From 48cc0167889d585a4ca0816731602a1797b9dd45 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 19 Sep 2020 12:00:55 +0200 Subject: [PATCH 132/183] Make go pass --- cucumber-expressions/go/custom_parameter_type_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber-expressions/go/custom_parameter_type_test.go b/cucumber-expressions/go/custom_parameter_type_test.go index b6f7df954a..39d7e132a9 100644 --- a/cucumber-expressions/go/custom_parameter_type_test.go +++ b/cucumber-expressions/go/custom_parameter_type_test.go @@ -53,7 +53,7 @@ func TestCustomParameterTypes(t *testing.T) { false, ) require.Error(t, err) - require.Equal(t, "illegal character '[' in parameter name {[string]}", err.Error()) + require.Equal(t, "Illegal character in parameter name {[string]}. Parameter names may not contain '[]()$.|?*+'", err.Error()) }) t.Run("CucumberExpression", func(t *testing.T) { From 1a02b2639d0291a6648893950a802fd92805d781 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 19 Sep 2020 12:02:57 +0200 Subject: [PATCH 133/183] Drop examples from js and ruby They can be synced later --- cucumber-expressions/javascript/examples.txt | 8 -------- cucumber-expressions/ruby/examples.txt | 8 -------- 2 files changed, 16 deletions(-) diff --git a/cucumber-expressions/javascript/examples.txt b/cucumber-expressions/javascript/examples.txt index c6acb6958e..645fba934c 100644 --- a/cucumber-expressions/javascript/examples.txt +++ b/cucumber-expressions/javascript/examples.txt @@ -29,11 +29,3 @@ I have 22 cukes in my belly now I have {} cuke(s) in my {} now I have 22 cukes in my belly now ["22","belly"] ---- -/^a (pre-commercial transaction |pre buyer fee model )?purchase(?: for \$(\d+))?$/ -a purchase for $33 -[null,33] ---- -Some ${float} of cukes at {int}° Celsius -Some $3.50 of cukes at 42° Celsius -[3.5,42] diff --git a/cucumber-expressions/ruby/examples.txt b/cucumber-expressions/ruby/examples.txt index c6acb6958e..645fba934c 100644 --- a/cucumber-expressions/ruby/examples.txt +++ b/cucumber-expressions/ruby/examples.txt @@ -29,11 +29,3 @@ I have 22 cukes in my belly now I have {} cuke(s) in my {} now I have 22 cukes in my belly now ["22","belly"] ---- -/^a (pre-commercial transaction |pre buyer fee model )?purchase(?: for \$(\d+))?$/ -a purchase for $33 -[null,33] ---- -Some ${float} of cukes at {int}° Celsius -Some $3.50 of cukes at 42° Celsius -[3.5,42] From 07865d7cbad81758ccac7059a3bbcbbc48eacc30 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 19 Sep 2020 12:06:12 +0200 Subject: [PATCH 134/183] configure sync for test data --- cucumber-expressions/go/.rsync | 1 + cucumber-expressions/java/.rsync | 1 + cucumber-expressions/javascript/.rsync | 1 + cucumber-expressions/ruby/.rsync | 1 + .../ast/alternation-followed-by-optional.yaml | 17 ++++++++++++ .../testdata/ast/alternation-phrase.yaml | 16 +++++++++++ .../ast/alternation-with-parameter.yaml | 27 +++++++++++++++++++ .../alternation-with-unused-end-optional.yaml | 15 +++++++++++ ...lternation-with-unused-start-optional.yaml | 8 ++++++ .../ast/alternation-with-white-space.yaml | 12 +++++++++ .../testdata/ast/alternation.yaml | 12 +++++++++ .../testdata/ast/anonymous-parameter.yaml | 5 ++++ .../testdata/ast/closing-brace.yaml | 5 ++++ .../testdata/ast/closing-parenthesis.yaml | 5 ++++ .../testdata/ast/empty-alternation.yaml | 8 ++++++ .../testdata/ast/empty-alternations.yaml | 9 +++++++ .../testdata/ast/empty-string.yaml | 3 +++ .../testdata/ast/escaped-alternation.yaml | 5 ++++ .../testdata/ast/escaped-backslash.yaml | 5 ++++ .../ast/escaped-opening-parenthesis.yaml | 5 ++++ ...escaped-optional-followed-by-optional.yaml | 15 +++++++++++ .../testdata/ast/escaped-optional-phrase.yaml | 10 +++++++ .../testdata/ast/escaped-optional.yaml | 7 +++++ .../testdata/ast/opening-brace.yaml | 8 ++++++ .../testdata/ast/opening-parenthesis.yaml | 8 ++++++ .../optional-containing-escaped-optional.yaml | 14 ++++++++++ .../testdata/ast/optional-phrase.yaml | 12 +++++++++ .../testdata/ast/optional.yaml | 7 +++++ .../testdata/ast/parameter.yaml | 7 +++++ cucumber-expressions/testdata/ast/phrase.yaml | 9 +++++++ .../testdata/ast/unfinished-parameter.yaml | 8 ++++++ ...lows-escaped-optional-parameter-types.yaml | 4 +++ ...llows-parameter-type-in-alternation-1.yaml | 4 +++ ...llows-parameter-type-in-alternation-2.yaml | 4 +++ ...low-parameter-adjacent-to-alternation.yaml | 5 ++++ ...lternative-by-adjacent-left-parameter.yaml | 10 +++++++ ...mpty-alternative-by-adjacent-optional.yaml | 9 +++++++ ...ternative-by-adjacent-right-parameter.yaml | 9 +++++++ ...ow-alternation-with-empty-alternative.yaml | 9 +++++++ .../does-not-allow-empty-optional.yaml | 9 +++++++ ...es-not-allow-optional-parameter-types.yaml | 9 +++++++ ...llow-parameter-type-with-left-bracket.yaml | 10 +++++++ .../does-not-match-misquoted-string.yaml | 4 +++ .../expression/doesnt-match-float-as-int.yaml | 5 ++++ ...tches-alternation-in-optional-as-text.yaml | 4 +++ .../expression/matches-alternation.yaml | 4 +++ .../matches-anonymous-parameter-type.yaml | 5 ++++ ...empty-string-along-with-other-strings.yaml | 4 +++ ...e-quoted-empty-string-as-empty-string.yaml | 4 +++ ...oted-string-with-escaped-double-quote.yaml | 4 +++ ...uble-quoted-string-with-single-quotes.yaml | 4 +++ .../matches-double-quoted-string.yaml | 4 +++ .../matches-doubly-escaped-parenthesis.yaml | 4 +++ .../matches-doubly-escaped-slash-1.yaml | 4 +++ .../matches-doubly-escaped-slash-2.yaml | 4 +++ .../matches-escaped-parenthesis-1.yaml | 4 +++ .../matches-escaped-parenthesis-2.yaml | 4 +++ .../matches-escaped-parenthesis-3.yaml | 4 +++ .../expression/matches-escaped-slash.yaml | 4 +++ .../testdata/expression/matches-float-1.yaml | 5 ++++ .../testdata/expression/matches-float-2.yaml | 5 ++++ .../testdata/expression/matches-int.yaml | 5 ++++ ...atches-multiple-double-quoted-strings.yaml | 4 +++ ...atches-multiple-single-quoted-strings.yaml | 4 +++ ...matches-optional-before-alternation-1.yaml | 4 +++ ...matches-optional-before-alternation-2.yaml | 4 +++ ...e-alternation-with-regex-characters-1.yaml | 4 +++ ...e-alternation-with-regex-characters-2.yaml | 4 +++ .../matches-optional-in-alternation-1.yaml | 5 ++++ .../matches-optional-in-alternation-2.yaml | 5 ++++ .../matches-optional-in-alternation-3.yaml | 5 ++++ ...empty-string-along-with-other-strings.yaml | 4 +++ ...e-quoted-empty-string-as-empty-string.yaml | 4 +++ ...ngle-quoted-string-with-double-quotes.yaml | 4 +++ ...oted-string-with-escaped-single-quote.yaml | 4 +++ .../matches-single-quoted-string.yaml | 4 +++ .../testdata/expression/matches-word.yaml | 4 +++ .../throws-unknown-parameter-type.yaml | 10 +++++++ .../testdata/tokens/alternation-phrase.yaml | 13 +++++++++ .../testdata/tokens/alternation.yaml | 9 +++++++ .../testdata/tokens/empty-string.yaml | 6 +++++ .../tokens/escape-non-reserved-character.yaml | 8 ++++++ .../testdata/tokens/escaped-alternation.yaml | 9 +++++++ ...ed-char-has-start-index-of-text-token.yaml | 9 +++++++ .../testdata/tokens/escaped-end-of-line.yaml | 8 ++++++ .../testdata/tokens/escaped-optional.yaml | 7 +++++ .../testdata/tokens/escaped-parameter.yaml | 7 +++++ .../testdata/tokens/escaped-space.yaml | 7 +++++ .../testdata/tokens/optional-phrase.yaml | 13 +++++++++ .../testdata/tokens/optional.yaml | 9 +++++++ .../testdata/tokens/parameter-phrase.yaml | 13 +++++++++ .../testdata/tokens/parameter.yaml | 9 +++++++ .../testdata/tokens/phrase.yaml | 11 ++++++++ 93 files changed, 644 insertions(+) create mode 100644 cucumber-expressions/testdata/ast/alternation-followed-by-optional.yaml create mode 100644 cucumber-expressions/testdata/ast/alternation-phrase.yaml create mode 100644 cucumber-expressions/testdata/ast/alternation-with-parameter.yaml create mode 100644 cucumber-expressions/testdata/ast/alternation-with-unused-end-optional.yaml create mode 100644 cucumber-expressions/testdata/ast/alternation-with-unused-start-optional.yaml create mode 100644 cucumber-expressions/testdata/ast/alternation-with-white-space.yaml create mode 100644 cucumber-expressions/testdata/ast/alternation.yaml create mode 100644 cucumber-expressions/testdata/ast/anonymous-parameter.yaml create mode 100644 cucumber-expressions/testdata/ast/closing-brace.yaml create mode 100644 cucumber-expressions/testdata/ast/closing-parenthesis.yaml create mode 100644 cucumber-expressions/testdata/ast/empty-alternation.yaml create mode 100644 cucumber-expressions/testdata/ast/empty-alternations.yaml create mode 100644 cucumber-expressions/testdata/ast/empty-string.yaml create mode 100644 cucumber-expressions/testdata/ast/escaped-alternation.yaml create mode 100644 cucumber-expressions/testdata/ast/escaped-backslash.yaml create mode 100644 cucumber-expressions/testdata/ast/escaped-opening-parenthesis.yaml create mode 100644 cucumber-expressions/testdata/ast/escaped-optional-followed-by-optional.yaml create mode 100644 cucumber-expressions/testdata/ast/escaped-optional-phrase.yaml create mode 100644 cucumber-expressions/testdata/ast/escaped-optional.yaml create mode 100644 cucumber-expressions/testdata/ast/opening-brace.yaml create mode 100644 cucumber-expressions/testdata/ast/opening-parenthesis.yaml create mode 100644 cucumber-expressions/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/testdata/ast/optional-phrase.yaml create mode 100644 cucumber-expressions/testdata/ast/optional.yaml create mode 100644 cucumber-expressions/testdata/ast/parameter.yaml create mode 100644 cucumber-expressions/testdata/ast/phrase.yaml create mode 100644 cucumber-expressions/testdata/ast/unfinished-parameter.yaml create mode 100644 cucumber-expressions/testdata/expression/allows-escaped-optional-parameter-types.yaml create mode 100644 cucumber-expressions/testdata/expression/allows-parameter-type-in-alternation-1.yaml create mode 100644 cucumber-expressions/testdata/expression/allows-parameter-type-in-alternation-2.yaml create mode 100644 cucumber-expressions/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-empty-optional.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-optional-parameter-types.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-match-misquoted-string.yaml create mode 100644 cucumber-expressions/testdata/expression/doesnt-match-float-as-int.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-alternation-in-optional-as-text.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-alternation.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-anonymous-parameter-type.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-double-quoted-string.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-doubly-escaped-parenthesis.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-doubly-escaped-slash-1.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-doubly-escaped-slash-2.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-escaped-parenthesis-1.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-escaped-parenthesis-2.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-escaped-parenthesis-3.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-escaped-slash.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-float-1.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-float-2.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-int.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-multiple-double-quoted-strings.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-multiple-single-quoted-strings.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-optional-before-alternation-1.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-optional-before-alternation-2.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-optional-in-alternation-1.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-optional-in-alternation-2.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-optional-in-alternation-3.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-single-quoted-string.yaml create mode 100644 cucumber-expressions/testdata/expression/matches-word.yaml create mode 100644 cucumber-expressions/testdata/expression/throws-unknown-parameter-type.yaml create mode 100644 cucumber-expressions/testdata/tokens/alternation-phrase.yaml create mode 100644 cucumber-expressions/testdata/tokens/alternation.yaml create mode 100644 cucumber-expressions/testdata/tokens/empty-string.yaml create mode 100644 cucumber-expressions/testdata/tokens/escape-non-reserved-character.yaml create mode 100644 cucumber-expressions/testdata/tokens/escaped-alternation.yaml create mode 100644 cucumber-expressions/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml create mode 100644 cucumber-expressions/testdata/tokens/escaped-end-of-line.yaml create mode 100644 cucumber-expressions/testdata/tokens/escaped-optional.yaml create mode 100644 cucumber-expressions/testdata/tokens/escaped-parameter.yaml create mode 100644 cucumber-expressions/testdata/tokens/escaped-space.yaml create mode 100644 cucumber-expressions/testdata/tokens/optional-phrase.yaml create mode 100644 cucumber-expressions/testdata/tokens/optional.yaml create mode 100644 cucumber-expressions/testdata/tokens/parameter-phrase.yaml create mode 100644 cucumber-expressions/testdata/tokens/parameter.yaml create mode 100644 cucumber-expressions/testdata/tokens/phrase.yaml diff --git a/cucumber-expressions/go/.rsync b/cucumber-expressions/go/.rsync index 1c4037d8da..738770370e 100644 --- a/cucumber-expressions/go/.rsync +++ b/cucumber-expressions/go/.rsync @@ -2,3 +2,4 @@ ../../.templates/github/ .github/ ../../.templates/go/ . ../examples.txt examples.txt +../testdata testdata diff --git a/cucumber-expressions/java/.rsync b/cucumber-expressions/java/.rsync index 74e8b453f4..1c14a92fa1 100644 --- a/cucumber-expressions/java/.rsync +++ b/cucumber-expressions/java/.rsync @@ -2,3 +2,4 @@ ../../.templates/github/ .github/ ../../.templates/java/ . ../examples.txt examples.txt +../testdata testdata diff --git a/cucumber-expressions/javascript/.rsync b/cucumber-expressions/javascript/.rsync index fe4ed7c3d0..bc7ef2bcbd 100644 --- a/cucumber-expressions/javascript/.rsync +++ b/cucumber-expressions/javascript/.rsync @@ -2,3 +2,4 @@ ../../.templates/github/ .github/ ../../.templates/javascript/ . ../examples.txt examples.txt +../testdata testdata diff --git a/cucumber-expressions/ruby/.rsync b/cucumber-expressions/ruby/.rsync index d101b2935c..d95d37f1ba 100644 --- a/cucumber-expressions/ruby/.rsync +++ b/cucumber-expressions/ruby/.rsync @@ -2,3 +2,4 @@ ../../.templates/github/ .github/ ../../.templates/ruby/ . ../examples.txt examples.txt +../testdata testdata diff --git a/cucumber-expressions/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/testdata/ast/alternation-followed-by-optional.yaml new file mode 100644 index 0000000000..0bfb53a534 --- /dev/null +++ b/cucumber-expressions/testdata/ast/alternation-followed-by-optional.yaml @@ -0,0 +1,17 @@ +expression: three blind\ rat/cat(s) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 16, "token": "blind rat"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 17, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 17, "end": 20, "token": "cat"}, + {"type": "OPTIONAL_NODE", "start": 20, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": "s"} + ]} + ]} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/testdata/ast/alternation-phrase.yaml new file mode 100644 index 0000000000..9a243822d5 --- /dev/null +++ b/cucumber-expressions/testdata/ast/alternation-phrase.yaml @@ -0,0 +1,16 @@ +expression: three hungry/blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 12, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 12, "token": "hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 13, "end": 18, "token": "blind"} + ]} + ]}, + {"type": "TEXT_NODE", "start": 18, "end": 19, "token": " "}, + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "mice"} + ]} diff --git a/cucumber-expressions/testdata/ast/alternation-with-parameter.yaml b/cucumber-expressions/testdata/ast/alternation-with-parameter.yaml new file mode 100644 index 0000000000..c5daf32bd7 --- /dev/null +++ b/cucumber-expressions/testdata/ast/alternation-with-parameter.yaml @@ -0,0 +1,27 @@ +expression: I select the {int}st/nd/rd/th +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "I"}, + {"type": "TEXT_NODE", "start": 1, "end": 2, "token": " "}, + {"type": "TEXT_NODE", "start": 2, "end": 8, "token": "select"}, + {"type": "TEXT_NODE", "start": 8, "end": 9, "token": " "}, + {"type": "TEXT_NODE", "start": 9, "end": 12, "token": "the"}, + {"type": "TEXT_NODE", "start": 12, "end": 13, "token": " "}, + {"type": "PARAMETER_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 14, "end": 17, "token": "int"} + ]}, + {"type": "ALTERNATION_NODE", "start": 18, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 18, "end": 20, "nodes": [ + {"type": "TEXT_NODE", "start": 18, "end": 20, "token": "st"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 21, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 23, "token": "nd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 24, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 24, "end": 26, "token": "rd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 27, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 27, "end": 29, "token": "th"} + ]} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/testdata/ast/alternation-with-unused-end-optional.yaml new file mode 100644 index 0000000000..842838b75f --- /dev/null +++ b/cucumber-expressions/testdata/ast/alternation-with-unused-end-optional.yaml @@ -0,0 +1,15 @@ +expression: three )blind\ mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 7, "token": ")"}, + {"type": "TEXT_NODE", "start": 7, "end": 18, "token": "blind mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 19, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/alternation-with-unused-start-optional.yaml b/cucumber-expressions/testdata/ast/alternation-with-unused-start-optional.yaml new file mode 100644 index 0000000000..e2f0584556 --- /dev/null +++ b/cucumber-expressions/testdata/ast/alternation-with-unused-start-optional.yaml @@ -0,0 +1,8 @@ +expression: three blind\ mice/rats( +exception: |- + This Cucumber Expression has a problem at column 23: + + three blind\ mice/rats( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/testdata/ast/alternation-with-white-space.yaml new file mode 100644 index 0000000000..eedd57dd21 --- /dev/null +++ b/cucumber-expressions/testdata/ast/alternation-with-white-space.yaml @@ -0,0 +1,12 @@ +expression: '\ three\ hungry/blind\ mice\ ' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 15, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 15, "token": " three hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 16, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 16, "end": 29, "token": "blind mice "} + ]} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/alternation.yaml b/cucumber-expressions/testdata/ast/alternation.yaml new file mode 100644 index 0000000000..88df8325fe --- /dev/null +++ b/cucumber-expressions/testdata/ast/alternation.yaml @@ -0,0 +1,12 @@ +expression: mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 4, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 4, "token": "mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 5, "end": 9, "nodes": [ + {"type": "TEXT_NODE", "start": 5, "end": 9, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/testdata/ast/anonymous-parameter.yaml new file mode 100644 index 0000000000..2c4d339333 --- /dev/null +++ b/cucumber-expressions/testdata/ast/anonymous-parameter.yaml @@ -0,0 +1,5 @@ +expression: "{}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 2, "nodes": []} + ]} diff --git a/cucumber-expressions/testdata/ast/closing-brace.yaml b/cucumber-expressions/testdata/ast/closing-brace.yaml new file mode 100644 index 0000000000..1bafd9c6a8 --- /dev/null +++ b/cucumber-expressions/testdata/ast/closing-brace.yaml @@ -0,0 +1,5 @@ +expression: "}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "}"} + ]} diff --git a/cucumber-expressions/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/testdata/ast/closing-parenthesis.yaml new file mode 100644 index 0000000000..23daf7bcd3 --- /dev/null +++ b/cucumber-expressions/testdata/ast/closing-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: ) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": ")"} + ]} diff --git a/cucumber-expressions/testdata/ast/empty-alternation.yaml b/cucumber-expressions/testdata/ast/empty-alternation.yaml new file mode 100644 index 0000000000..6d810fc8f3 --- /dev/null +++ b/cucumber-expressions/testdata/ast/empty-alternation.yaml @@ -0,0 +1,8 @@ +expression: / +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/empty-alternations.yaml b/cucumber-expressions/testdata/ast/empty-alternations.yaml new file mode 100644 index 0000000000..f8d4dd4cf8 --- /dev/null +++ b/cucumber-expressions/testdata/ast/empty-alternations.yaml @@ -0,0 +1,9 @@ +expression: '//' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/empty-string.yaml b/cucumber-expressions/testdata/ast/empty-string.yaml new file mode 100644 index 0000000000..4d33c2dc76 --- /dev/null +++ b/cucumber-expressions/testdata/ast/empty-string.yaml @@ -0,0 +1,3 @@ +expression: "" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 0, "nodes": []} diff --git a/cucumber-expressions/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/testdata/ast/escaped-alternation.yaml new file mode 100644 index 0000000000..3ed9c37674 --- /dev/null +++ b/cucumber-expressions/testdata/ast/escaped-alternation.yaml @@ -0,0 +1,5 @@ +expression: 'mice\/rats' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 10, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 10, "token": "mice/rats"} + ]} diff --git a/cucumber-expressions/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/testdata/ast/escaped-backslash.yaml new file mode 100644 index 0000000000..da2d008e1e --- /dev/null +++ b/cucumber-expressions/testdata/ast/escaped-backslash.yaml @@ -0,0 +1,5 @@ +expression: '\\' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\\"} + ]} diff --git a/cucumber-expressions/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/testdata/ast/escaped-opening-parenthesis.yaml new file mode 100644 index 0000000000..afafc59eb8 --- /dev/null +++ b/cucumber-expressions/testdata/ast/escaped-opening-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: '\(' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "("} + ]} diff --git a/cucumber-expressions/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/testdata/ast/escaped-optional-followed-by-optional.yaml new file mode 100644 index 0000000000..1e4746291b --- /dev/null +++ b/cucumber-expressions/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -0,0 +1,15 @@ +expression: three \((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 8, "token": "("}, + {"type": "OPTIONAL_NODE", "start": 8, "end": 14, "nodes": [ + {"type": "TEXT_NODE", "start": 9, "end": 13, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": ")"}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/testdata/ast/escaped-optional-phrase.yaml new file mode 100644 index 0000000000..832249e2a7 --- /dev/null +++ b/cucumber-expressions/testdata/ast/escaped-optional-phrase.yaml @@ -0,0 +1,10 @@ +expression: three \(blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 19, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 13, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": ")"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 19, "token": "mice"} + ]} diff --git a/cucumber-expressions/testdata/ast/escaped-optional.yaml b/cucumber-expressions/testdata/ast/escaped-optional.yaml new file mode 100644 index 0000000000..4c2b457d6f --- /dev/null +++ b/cucumber-expressions/testdata/ast/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: '\(blind)' + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 7, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": ")"} + ]} diff --git a/cucumber-expressions/testdata/ast/opening-brace.yaml b/cucumber-expressions/testdata/ast/opening-brace.yaml new file mode 100644 index 0000000000..916a674a36 --- /dev/null +++ b/cucumber-expressions/testdata/ast/opening-brace.yaml @@ -0,0 +1,8 @@ +expression: '{' +exception: |- + This Cucumber Expression has a problem at column 1: + + { + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/testdata/ast/opening-parenthesis.yaml new file mode 100644 index 0000000000..929d6ae304 --- /dev/null +++ b/cucumber-expressions/testdata/ast/opening-parenthesis.yaml @@ -0,0 +1,8 @@ +expression: ( +exception: |- + This Cucumber Expression has a problem at column 1: + + ( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/testdata/ast/optional-containing-escaped-optional.yaml new file mode 100644 index 0000000000..f09199a454 --- /dev/null +++ b/cucumber-expressions/testdata/ast/optional-containing-escaped-optional.yaml @@ -0,0 +1,14 @@ +expression: three ((very\) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, + {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/testdata/ast/optional-phrase.yaml b/cucumber-expressions/testdata/ast/optional-phrase.yaml new file mode 100644 index 0000000000..0ef4fb3f95 --- /dev/null +++ b/cucumber-expressions/testdata/ast/optional-phrase.yaml @@ -0,0 +1,12 @@ +expression: three (blind) mice + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 12, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 18, "token": "mice"} + ]} diff --git a/cucumber-expressions/testdata/ast/optional.yaml b/cucumber-expressions/testdata/ast/optional.yaml new file mode 100644 index 0000000000..6ce2b632e7 --- /dev/null +++ b/cucumber-expressions/testdata/ast/optional.yaml @@ -0,0 +1,7 @@ +expression: (blind) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 6, "token": "blind"} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/parameter.yaml b/cucumber-expressions/testdata/ast/parameter.yaml new file mode 100644 index 0000000000..92ac8c147e --- /dev/null +++ b/cucumber-expressions/testdata/ast/parameter.yaml @@ -0,0 +1,7 @@ +expression: "{string}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 7, "token": "string"} + ]} + ]} diff --git a/cucumber-expressions/testdata/ast/phrase.yaml b/cucumber-expressions/testdata/ast/phrase.yaml new file mode 100644 index 0000000000..ba340d0122 --- /dev/null +++ b/cucumber-expressions/testdata/ast/phrase.yaml @@ -0,0 +1,9 @@ +expression: three blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 11, "token": "blind"}, + {"type": "TEXT_NODE", "start": 11, "end": 12, "token": " "}, + {"type": "TEXT_NODE", "start": 12, "end": 16, "token": "mice"} + ]} diff --git a/cucumber-expressions/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/testdata/ast/unfinished-parameter.yaml new file mode 100644 index 0000000000..d02f9b4ccf --- /dev/null +++ b/cucumber-expressions/testdata/ast/unfinished-parameter.yaml @@ -0,0 +1,8 @@ +expression: "{string" +exception: |- + This Cucumber Expression has a problem at column 1: + + {string + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/testdata/expression/allows-escaped-optional-parameter-types.yaml b/cucumber-expressions/testdata/expression/allows-escaped-optional-parameter-types.yaml new file mode 100644 index 0000000000..a00b45acef --- /dev/null +++ b/cucumber-expressions/testdata/expression/allows-escaped-optional-parameter-types.yaml @@ -0,0 +1,4 @@ +expression: \({int}) +text: (3) +expected: |- + [3] diff --git a/cucumber-expressions/testdata/expression/allows-parameter-type-in-alternation-1.yaml b/cucumber-expressions/testdata/expression/allows-parameter-type-in-alternation-1.yaml new file mode 100644 index 0000000000..bb1a6f21b1 --- /dev/null +++ b/cucumber-expressions/testdata/expression/allows-parameter-type-in-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: i18n +expected: |- + [18] diff --git a/cucumber-expressions/testdata/expression/allows-parameter-type-in-alternation-2.yaml b/cucumber-expressions/testdata/expression/allows-parameter-type-in-alternation-2.yaml new file mode 100644 index 0000000000..cdddce7d84 --- /dev/null +++ b/cucumber-expressions/testdata/expression/allows-parameter-type-in-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: a11y +expected: |- + [11] diff --git a/cucumber-expressions/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml b/cucumber-expressions/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml new file mode 100644 index 0000000000..9e2ecdfbe1 --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml @@ -0,0 +1,5 @@ +expression: |- + {int}st/nd/rd/th +text: 3rd +expected: |- + [3] diff --git a/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml b/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml new file mode 100644 index 0000000000..b32540a4a9 --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml @@ -0,0 +1,10 @@ +expression: |- + {int}/x +text: '3' +exception: |- + This Cucumber Expression has a problem at column 6: + + {int}/x + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml b/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml new file mode 100644 index 0000000000..a0aab0e5a9 --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml @@ -0,0 +1,9 @@ +expression: three (brown)/black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (brown)/black mice + ^-----^ + An alternative may not exclusively contain optionals. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml b/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml new file mode 100644 index 0000000000..50250f00aa --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml @@ -0,0 +1,9 @@ +expression: x/{int} +text: '3' +exception: |- + This Cucumber Expression has a problem at column 3: + + x/{int} + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml b/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml new file mode 100644 index 0000000000..b724cfa77f --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml @@ -0,0 +1,9 @@ +expression: three brown//black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three brown//black mice + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/testdata/expression/does-not-allow-empty-optional.yaml b/cucumber-expressions/testdata/expression/does-not-allow-empty-optional.yaml new file mode 100644 index 0000000000..00e341af0b --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-empty-optional.yaml @@ -0,0 +1,9 @@ +expression: three () mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three () mice + ^^ + An optional must contain some text. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/testdata/expression/does-not-allow-optional-parameter-types.yaml b/cucumber-expressions/testdata/expression/does-not-allow-optional-parameter-types.yaml new file mode 100644 index 0000000000..b88061e9b4 --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-optional-parameter-types.yaml @@ -0,0 +1,9 @@ +expression: ({int}) +text: '3' +exception: |- + This Cucumber Expression has a problem at column 2: + + ({int}) + ^---^ + An optional may not contain a parameter type. + If you did not mean to use an parameter type you can use '\{' to escape the the '{' diff --git a/cucumber-expressions/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml new file mode 100644 index 0000000000..1dd65aa276 --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml @@ -0,0 +1,10 @@ +expression: |- + {[string]} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {[string]} + ^--------^ + Parameter names may not contain '[]()$.|?*+'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/testdata/expression/does-not-match-misquoted-string.yaml b/cucumber-expressions/testdata/expression/does-not-match-misquoted-string.yaml new file mode 100644 index 0000000000..18023180af --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-match-misquoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind' mice +expected: |- + null diff --git a/cucumber-expressions/testdata/expression/doesnt-match-float-as-int.yaml b/cucumber-expressions/testdata/expression/doesnt-match-float-as-int.yaml new file mode 100644 index 0000000000..d66b586430 --- /dev/null +++ b/cucumber-expressions/testdata/expression/doesnt-match-float-as-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '1.22' +expected: |- + null diff --git a/cucumber-expressions/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/testdata/expression/matches-alternation-in-optional-as-text.yaml new file mode 100644 index 0000000000..15fe78bf53 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-alternation-in-optional-as-text.yaml @@ -0,0 +1,4 @@ +expression: three( brown/black) mice +text: three brown/black mice +expected: |- + [] diff --git a/cucumber-expressions/testdata/expression/matches-alternation.yaml b/cucumber-expressions/testdata/expression/matches-alternation.yaml new file mode 100644 index 0000000000..20a9b9a728 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-alternation.yaml @@ -0,0 +1,4 @@ +expression: mice/rats and rats\/mice +text: rats and rats/mice +expected: |- + [] diff --git a/cucumber-expressions/testdata/expression/matches-anonymous-parameter-type.yaml b/cucumber-expressions/testdata/expression/matches-anonymous-parameter-type.yaml new file mode 100644 index 0000000000..fc954960df --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-anonymous-parameter-type.yaml @@ -0,0 +1,5 @@ +expression: |- + {} +text: '0.22' +expected: |- + ["0.22"] diff --git a/cucumber-expressions/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c3e1962e22 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "" and "handsome" mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..89315b62ef --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "" mice +expected: |- + [""] diff --git a/cucumber-expressions/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml b/cucumber-expressions/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml new file mode 100644 index 0000000000..fe30a044c1 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "bl\"nd" mice +expected: |- + ["bl\"nd"] diff --git a/cucumber-expressions/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml b/cucumber-expressions/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml new file mode 100644 index 0000000000..25fcf304c1 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "'blind'" mice +expected: |- + ["'blind'"] diff --git a/cucumber-expressions/testdata/expression/matches-double-quoted-string.yaml b/cucumber-expressions/testdata/expression/matches-double-quoted-string.yaml new file mode 100644 index 0000000000..8b0560f332 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-double-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/testdata/expression/matches-doubly-escaped-parenthesis.yaml b/cucumber-expressions/testdata/expression/matches-doubly-escaped-parenthesis.yaml new file mode 100644 index 0000000000..902a084103 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-doubly-escaped-parenthesis.yaml @@ -0,0 +1,4 @@ +expression: three \\(exceptionally) \\{string} mice +text: three \exceptionally \"blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/testdata/expression/matches-doubly-escaped-slash-1.yaml b/cucumber-expressions/testdata/expression/matches-doubly-escaped-slash-1.yaml new file mode 100644 index 0000000000..94e531eca7 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-doubly-escaped-slash-1.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: 12\ +expected: |- + [] diff --git a/cucumber-expressions/testdata/expression/matches-doubly-escaped-slash-2.yaml b/cucumber-expressions/testdata/expression/matches-doubly-escaped-slash-2.yaml new file mode 100644 index 0000000000..9c9c735b86 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-doubly-escaped-slash-2.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: '2020' +expected: |- + [] diff --git a/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-1.yaml b/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-1.yaml new file mode 100644 index 0000000000..171df89ee1 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-1.yaml @@ -0,0 +1,4 @@ +expression: three \(exceptionally) \{string} mice +text: three (exceptionally) {string} mice +expected: |- + [] diff --git a/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-2.yaml b/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-2.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-2.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-3.yaml b/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-3.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-escaped-parenthesis-3.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/testdata/expression/matches-escaped-slash.yaml b/cucumber-expressions/testdata/expression/matches-escaped-slash.yaml new file mode 100644 index 0000000000..d8b3933399 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-escaped-slash.yaml @@ -0,0 +1,4 @@ +expression: 12\/2020 +text: 12/2020 +expected: |- + [] diff --git a/cucumber-expressions/testdata/expression/matches-float-1.yaml b/cucumber-expressions/testdata/expression/matches-float-1.yaml new file mode 100644 index 0000000000..fe7e8b1869 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-float-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '0.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/testdata/expression/matches-float-2.yaml b/cucumber-expressions/testdata/expression/matches-float-2.yaml new file mode 100644 index 0000000000..c1e5894eac --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-float-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/testdata/expression/matches-int.yaml b/cucumber-expressions/testdata/expression/matches-int.yaml new file mode 100644 index 0000000000..bcd3763886 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '22' +expected: |- + [22] diff --git a/cucumber-expressions/testdata/expression/matches-multiple-double-quoted-strings.yaml b/cucumber-expressions/testdata/expression/matches-multiple-double-quoted-strings.yaml new file mode 100644 index 0000000000..6c74bc2350 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-multiple-double-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "blind" and "crippled" mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/testdata/expression/matches-multiple-single-quoted-strings.yaml b/cucumber-expressions/testdata/expression/matches-multiple-single-quoted-strings.yaml new file mode 100644 index 0000000000..5037821c14 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-multiple-single-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three 'blind' and 'crippled' mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/testdata/expression/matches-optional-before-alternation-1.yaml b/cucumber-expressions/testdata/expression/matches-optional-before-alternation-1.yaml new file mode 100644 index 0000000000..821776715b --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-optional-before-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three brown mice +expected: |- + [] diff --git a/cucumber-expressions/testdata/expression/matches-optional-before-alternation-2.yaml b/cucumber-expressions/testdata/expression/matches-optional-before-alternation-2.yaml new file mode 100644 index 0000000000..71b3a341f1 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-optional-before-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three rats +expected: |- + [] diff --git a/cucumber-expressions/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml b/cucumber-expressions/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml new file mode 100644 index 0000000000..2632f410ce --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 2 seconds? +expected: |- + [2] diff --git a/cucumber-expressions/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml b/cucumber-expressions/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml new file mode 100644 index 0000000000..7b30f667bc --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 1 second. +expected: |- + [1] diff --git a/cucumber-expressions/testdata/expression/matches-optional-in-alternation-1.yaml b/cucumber-expressions/testdata/expression/matches-optional-in-alternation-1.yaml new file mode 100644 index 0000000000..6574bb4bdf --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-optional-in-alternation-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 3 rats +expected: |- + [3] diff --git a/cucumber-expressions/testdata/expression/matches-optional-in-alternation-2.yaml b/cucumber-expressions/testdata/expression/matches-optional-in-alternation-2.yaml new file mode 100644 index 0000000000..4eb0f0e1c6 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-optional-in-alternation-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 2 mice +expected: |- + [2] diff --git a/cucumber-expressions/testdata/expression/matches-optional-in-alternation-3.yaml b/cucumber-expressions/testdata/expression/matches-optional-in-alternation-3.yaml new file mode 100644 index 0000000000..964fa6489e --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-optional-in-alternation-3.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 1 mouse +expected: |- + [1] diff --git a/cucumber-expressions/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c963dcf1c7 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three '' and 'handsome' mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..cff2a2d1ec --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '' mice +expected: |- + [""] diff --git a/cucumber-expressions/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml b/cucumber-expressions/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml new file mode 100644 index 0000000000..eb9ed537cd --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '"blind"' mice +expected: |- + ["\"blind\""] diff --git a/cucumber-expressions/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml b/cucumber-expressions/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml new file mode 100644 index 0000000000..4c2b0055b9 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'bl\'nd' mice +expected: |- + ["bl'nd"] diff --git a/cucumber-expressions/testdata/expression/matches-single-quoted-string.yaml b/cucumber-expressions/testdata/expression/matches-single-quoted-string.yaml new file mode 100644 index 0000000000..6c8f4652a5 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-single-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'blind' mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/testdata/expression/matches-word.yaml b/cucumber-expressions/testdata/expression/matches-word.yaml new file mode 100644 index 0000000000..358fd3afd1 --- /dev/null +++ b/cucumber-expressions/testdata/expression/matches-word.yaml @@ -0,0 +1,4 @@ +expression: three {word} mice +text: three blind mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/testdata/expression/throws-unknown-parameter-type.yaml b/cucumber-expressions/testdata/expression/throws-unknown-parameter-type.yaml new file mode 100644 index 0000000000..384e3a48c3 --- /dev/null +++ b/cucumber-expressions/testdata/expression/throws-unknown-parameter-type.yaml @@ -0,0 +1,10 @@ +expression: |- + {unknown} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {unknown} + ^-------^ + Undefined parameter type 'unknown'. + Please register a ParameterType for 'unknown' diff --git a/cucumber-expressions/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/testdata/tokens/alternation-phrase.yaml new file mode 100644 index 0000000000..48b107f64e --- /dev/null +++ b/cucumber-expressions/testdata/tokens/alternation-phrase.yaml @@ -0,0 +1,13 @@ +expression: three blind/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "ALTERNATION", "start": 11, "end": 12, "text": "/"}, + {"type": "TEXT", "start": 12, "end": 19, "text": "cripple"}, + {"type": "WHITE_SPACE", "start": 19, "end": 20, "text": " "}, + {"type": "TEXT", "start": 20, "end": 24, "text": "mice"}, + {"type": "END_OF_LINE", "start": 24, "end": 24, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/alternation.yaml b/cucumber-expressions/testdata/tokens/alternation.yaml new file mode 100644 index 0000000000..a4920f22e5 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/alternation.yaml @@ -0,0 +1,9 @@ +expression: blind/cripple +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "blind"}, + {"type": "ALTERNATION", "start": 5, "end": 6, "text": "/"}, + {"type": "TEXT", "start": 6, "end": 13, "text": "cripple"}, + {"type": "END_OF_LINE", "start": 13, "end": 13, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/empty-string.yaml b/cucumber-expressions/testdata/tokens/empty-string.yaml new file mode 100644 index 0000000000..501f7522f2 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/empty-string.yaml @@ -0,0 +1,6 @@ +expression: "" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "END_OF_LINE", "start": 0, "end": 0, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/testdata/tokens/escape-non-reserved-character.yaml new file mode 100644 index 0000000000..5e206be084 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/escape-non-reserved-character.yaml @@ -0,0 +1,8 @@ +expression: \[ +exception: |- + This Cucumber Expression has a problem at column 2: + + \[ + ^ + Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. + If you did mean to use an '\' you can use '\\' to escape it diff --git a/cucumber-expressions/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/testdata/tokens/escaped-alternation.yaml new file mode 100644 index 0000000000..7e21f7ad19 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/escaped-alternation.yaml @@ -0,0 +1,9 @@ +expression: blind\ and\ famished\/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 29, "text": "blind and famished/cripple"}, + {"type": "WHITE_SPACE", "start": 29, "end": 30, "text": " "}, + {"type": "TEXT", "start": 30, "end": 34, "text": "mice"}, + {"type": "END_OF_LINE", "start": 34, "end": 34, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml b/cucumber-expressions/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml new file mode 100644 index 0000000000..6375ad52a5 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml @@ -0,0 +1,9 @@ +expression: ' \/ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "WHITE_SPACE", "start": 0, "end": 1, "text": " "}, + {"type": "TEXT", "start": 1, "end": 3, "text": "/"}, + {"type": "WHITE_SPACE", "start": 3, "end": 4, "text": " "}, + {"type": "END_OF_LINE", "start": 4, "end": 4, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/testdata/tokens/escaped-end-of-line.yaml new file mode 100644 index 0000000000..a1bd00fd98 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/escaped-end-of-line.yaml @@ -0,0 +1,8 @@ +expression: \ +exception: |- + This Cucumber Expression has a problem at column 1: + + \ + ^ + The end of line can not be escaped. + You can use '\\' to escape the the '\' diff --git a/cucumber-expressions/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/testdata/tokens/escaped-optional.yaml new file mode 100644 index 0000000000..2b365b581c --- /dev/null +++ b/cucumber-expressions/testdata/tokens/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: \(blind\) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 9, "text": "(blind)"}, + {"type": "END_OF_LINE", "start": 9, "end": 9, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/testdata/tokens/escaped-parameter.yaml new file mode 100644 index 0000000000..2cdac4f35a --- /dev/null +++ b/cucumber-expressions/testdata/tokens/escaped-parameter.yaml @@ -0,0 +1,7 @@ +expression: \{string\} +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 10, "text": "{string}"}, + {"type": "END_OF_LINE", "start": 10, "end": 10, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/escaped-space.yaml b/cucumber-expressions/testdata/tokens/escaped-space.yaml new file mode 100644 index 0000000000..912827a941 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/escaped-space.yaml @@ -0,0 +1,7 @@ +expression: '\ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 2, "text": " "}, + {"type": "END_OF_LINE", "start": 2, "end": 2, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/testdata/tokens/optional-phrase.yaml new file mode 100644 index 0000000000..2ddc6bb502 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/optional-phrase.yaml @@ -0,0 +1,13 @@ +expression: three (blind) mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_OPTIONAL", "start": 6, "end": 7, "text": "("}, + {"type": "TEXT", "start": 7, "end": 12, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 12, "end": 13, "text": ")"}, + {"type": "WHITE_SPACE", "start": 13, "end": 14, "text": " "}, + {"type": "TEXT", "start": 14, "end": 18, "text": "mice"}, + {"type": "END_OF_LINE", "start": 18, "end": 18, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/optional.yaml b/cucumber-expressions/testdata/tokens/optional.yaml new file mode 100644 index 0000000000..35b1474a7c --- /dev/null +++ b/cucumber-expressions/testdata/tokens/optional.yaml @@ -0,0 +1,9 @@ +expression: (blind) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_OPTIONAL", "start": 0, "end": 1, "text": "("}, + {"type": "TEXT", "start": 1, "end": 6, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 6, "end": 7, "text": ")"}, + {"type": "END_OF_LINE", "start": 7, "end": 7, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/testdata/tokens/parameter-phrase.yaml new file mode 100644 index 0000000000..5e98055ee6 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/parameter-phrase.yaml @@ -0,0 +1,13 @@ +expression: three {string} mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_PARAMETER", "start": 6, "end": 7, "text": "{"}, + {"type": "TEXT", "start": 7, "end": 13, "text": "string"}, + {"type": "END_PARAMETER", "start": 13, "end": 14, "text": "}"}, + {"type": "WHITE_SPACE", "start": 14, "end": 15, "text": " "}, + {"type": "TEXT", "start": 15, "end": 19, "text": "mice"}, + {"type": "END_OF_LINE", "start": 19, "end": 19, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/parameter.yaml b/cucumber-expressions/testdata/tokens/parameter.yaml new file mode 100644 index 0000000000..460363c393 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/parameter.yaml @@ -0,0 +1,9 @@ +expression: "{string}" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_PARAMETER", "start": 0, "end": 1, "text": "{"}, + {"type": "TEXT", "start": 1, "end": 7, "text": "string"}, + {"type": "END_PARAMETER", "start": 7, "end": 8, "text": "}"}, + {"type": "END_OF_LINE", "start": 8, "end": 8, "text": ""} + ] diff --git a/cucumber-expressions/testdata/tokens/phrase.yaml b/cucumber-expressions/testdata/tokens/phrase.yaml new file mode 100644 index 0000000000..e2cfccf7b4 --- /dev/null +++ b/cucumber-expressions/testdata/tokens/phrase.yaml @@ -0,0 +1,11 @@ +expression: three blind mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "WHITE_SPACE", "start": 11, "end": 12, "text": " "}, + {"type": "TEXT", "start": 12, "end": 16, "text": "mice"}, + {"type": "END_OF_LINE", "start": 16, "end": 16, "text": ""} + ] From e24e84c13762b22940177b68c289d4b97d6b8b7a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 19 Sep 2020 12:09:38 +0200 Subject: [PATCH 135/183] Revert a bit more on ruby/js examples --- cucumber-expressions/javascript/examples.txt | 2 +- cucumber-expressions/ruby/examples.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cucumber-expressions/javascript/examples.txt b/cucumber-expressions/javascript/examples.txt index 645fba934c..12bee4dc01 100644 --- a/cucumber-expressions/javascript/examples.txt +++ b/cucumber-expressions/javascript/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \\[]^$.|?*+ +I have {int} cuke(s) and some \[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- diff --git a/cucumber-expressions/ruby/examples.txt b/cucumber-expressions/ruby/examples.txt index 645fba934c..12bee4dc01 100644 --- a/cucumber-expressions/ruby/examples.txt +++ b/cucumber-expressions/ruby/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \\[]^$.|?*+ +I have {int} cuke(s) and some \[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- From 3b434adbca4a328a8af4dff2fa44292da6cd5759 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 19 Sep 2020 12:11:53 +0200 Subject: [PATCH 136/183] Fix sync --- cucumber-expressions/go/.rsync | 2 +- cucumber-expressions/go/cucumber_expression_parser_test.go | 2 +- cucumber-expressions/go/cucumber_expression_test.go | 2 +- cucumber-expressions/go/cucumber_expression_tokenizer_test.go | 2 +- cucumber-expressions/java/.rsync | 2 +- cucumber-expressions/javascript/.rsync | 2 +- cucumber-expressions/ruby/.rsync | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cucumber-expressions/go/.rsync b/cucumber-expressions/go/.rsync index 738770370e..1831697da5 100644 --- a/cucumber-expressions/go/.rsync +++ b/cucumber-expressions/go/.rsync @@ -2,4 +2,4 @@ ../../.templates/github/ .github/ ../../.templates/go/ . ../examples.txt examples.txt -../testdata testdata +../testdata . diff --git a/cucumber-expressions/go/cucumber_expression_parser_test.go b/cucumber-expressions/go/cucumber_expression_parser_test.go index 5db2ef6b63..8d2077f62c 100644 --- a/cucumber-expressions/go/cucumber_expression_parser_test.go +++ b/cucumber-expressions/go/cucumber_expression_parser_test.go @@ -22,7 +22,7 @@ func TestCucumberExpressionParser(t *testing.T) { require.Equal(t, expected, err.Error()) } - directory := "../java/testdata/ast/" + directory := "testdata/ast/" files, err := ioutil.ReadDir(directory) require.NoError(t, err) diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 6245bbfe20..9820a78e6f 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -45,7 +45,7 @@ func TestCucumberExpression(t *testing.T) { } } - directory := "../java/testdata/expression/" + directory := "testdata/expression/" files, err := ioutil.ReadDir(directory) require.NoError(t, err) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go index ef984de62e..f7a38470c0 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer_test.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer_test.go @@ -18,7 +18,7 @@ type expectation struct { func TestCucumberExpressionTokenizer(t *testing.T) { - directory := "../java/testdata/tokens/" + directory := "testdata/tokens/" files, err := ioutil.ReadDir(directory) require.NoError(t, err) diff --git a/cucumber-expressions/java/.rsync b/cucumber-expressions/java/.rsync index 1c14a92fa1..a27b58d5a7 100644 --- a/cucumber-expressions/java/.rsync +++ b/cucumber-expressions/java/.rsync @@ -2,4 +2,4 @@ ../../.templates/github/ .github/ ../../.templates/java/ . ../examples.txt examples.txt -../testdata testdata +../testdata . diff --git a/cucumber-expressions/javascript/.rsync b/cucumber-expressions/javascript/.rsync index bc7ef2bcbd..998c00c802 100644 --- a/cucumber-expressions/javascript/.rsync +++ b/cucumber-expressions/javascript/.rsync @@ -2,4 +2,4 @@ ../../.templates/github/ .github/ ../../.templates/javascript/ . ../examples.txt examples.txt -../testdata testdata +../testdata . diff --git a/cucumber-expressions/ruby/.rsync b/cucumber-expressions/ruby/.rsync index d95d37f1ba..0b4ed5255f 100644 --- a/cucumber-expressions/ruby/.rsync +++ b/cucumber-expressions/ruby/.rsync @@ -2,4 +2,4 @@ ../../.templates/github/ .github/ ../../.templates/ruby/ . ../examples.txt examples.txt -../testdata testdata +../testdata . From c2b50ad188b61e1fb8576a565ba2339c79bc7d1a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 19 Sep 2020 12:16:29 +0200 Subject: [PATCH 137/183] Sync test data for go --- .../ast/alternation-followed-by-optional.yaml | 17 ++++++++++++ .../go/testdata/ast/alternation-phrase.yaml | 16 +++++++++++ .../ast/alternation-with-parameter.yaml | 27 +++++++++++++++++++ .../alternation-with-unused-end-optional.yaml | 15 +++++++++++ ...lternation-with-unused-start-optional.yaml | 8 ++++++ .../ast/alternation-with-white-space.yaml | 12 +++++++++ .../go/testdata/ast/alternation.yaml | 12 +++++++++ .../go/testdata/ast/anonymous-parameter.yaml | 5 ++++ .../go/testdata/ast/closing-brace.yaml | 5 ++++ .../go/testdata/ast/closing-parenthesis.yaml | 5 ++++ .../go/testdata/ast/empty-alternation.yaml | 8 ++++++ .../go/testdata/ast/empty-alternations.yaml | 9 +++++++ .../go/testdata/ast/empty-string.yaml | 3 +++ .../go/testdata/ast/escaped-alternation.yaml | 5 ++++ .../go/testdata/ast/escaped-backslash.yaml | 5 ++++ .../ast/escaped-opening-parenthesis.yaml | 5 ++++ ...escaped-optional-followed-by-optional.yaml | 15 +++++++++++ .../testdata/ast/escaped-optional-phrase.yaml | 10 +++++++ .../go/testdata/ast/escaped-optional.yaml | 7 +++++ .../go/testdata/ast/opening-brace.yaml | 8 ++++++ .../go/testdata/ast/opening-parenthesis.yaml | 8 ++++++ .../optional-containing-escaped-optional.yaml | 14 ++++++++++ .../go/testdata/ast/optional-phrase.yaml | 12 +++++++++ .../go/testdata/ast/optional.yaml | 7 +++++ .../go/testdata/ast/parameter.yaml | 7 +++++ .../go/testdata/ast/phrase.yaml | 9 +++++++ .../go/testdata/ast/unfinished-parameter.yaml | 8 ++++++ ...lows-escaped-optional-parameter-types.yaml | 4 +++ ...llows-parameter-type-in-alternation-1.yaml | 4 +++ ...llows-parameter-type-in-alternation-2.yaml | 4 +++ ...low-parameter-adjacent-to-alternation.yaml | 5 ++++ ...lternative-by-adjacent-left-parameter.yaml | 10 +++++++ ...mpty-alternative-by-adjacent-optional.yaml | 9 +++++++ ...ternative-by-adjacent-right-parameter.yaml | 9 +++++++ ...ow-alternation-with-empty-alternative.yaml | 9 +++++++ .../does-not-allow-empty-optional.yaml | 9 +++++++ ...es-not-allow-optional-parameter-types.yaml | 9 +++++++ ...llow-parameter-type-with-left-bracket.yaml | 10 +++++++ .../does-not-match-misquoted-string.yaml | 4 +++ .../expression/doesnt-match-float-as-int.yaml | 5 ++++ ...tches-alternation-in-optional-as-text.yaml | 4 +++ .../expression/matches-alternation.yaml | 4 +++ .../matches-anonymous-parameter-type.yaml | 5 ++++ ...empty-string-along-with-other-strings.yaml | 4 +++ ...e-quoted-empty-string-as-empty-string.yaml | 4 +++ ...oted-string-with-escaped-double-quote.yaml | 4 +++ ...uble-quoted-string-with-single-quotes.yaml | 4 +++ .../matches-double-quoted-string.yaml | 4 +++ .../matches-doubly-escaped-parenthesis.yaml | 4 +++ .../matches-doubly-escaped-slash-1.yaml | 4 +++ .../matches-doubly-escaped-slash-2.yaml | 4 +++ .../matches-escaped-parenthesis-1.yaml | 4 +++ .../matches-escaped-parenthesis-2.yaml | 4 +++ .../matches-escaped-parenthesis-3.yaml | 4 +++ .../expression/matches-escaped-slash.yaml | 4 +++ .../testdata/expression/matches-float-1.yaml | 5 ++++ .../testdata/expression/matches-float-2.yaml | 5 ++++ .../go/testdata/expression/matches-int.yaml | 5 ++++ ...atches-multiple-double-quoted-strings.yaml | 4 +++ ...atches-multiple-single-quoted-strings.yaml | 4 +++ ...matches-optional-before-alternation-1.yaml | 4 +++ ...matches-optional-before-alternation-2.yaml | 4 +++ ...e-alternation-with-regex-characters-1.yaml | 4 +++ ...e-alternation-with-regex-characters-2.yaml | 4 +++ .../matches-optional-in-alternation-1.yaml | 5 ++++ .../matches-optional-in-alternation-2.yaml | 5 ++++ .../matches-optional-in-alternation-3.yaml | 5 ++++ ...empty-string-along-with-other-strings.yaml | 4 +++ ...e-quoted-empty-string-as-empty-string.yaml | 4 +++ ...ngle-quoted-string-with-double-quotes.yaml | 4 +++ ...oted-string-with-escaped-single-quote.yaml | 4 +++ .../matches-single-quoted-string.yaml | 4 +++ .../go/testdata/expression/matches-word.yaml | 4 +++ .../throws-unknown-parameter-type.yaml | 10 +++++++ .../testdata/tokens/alternation-phrase.yaml | 13 +++++++++ .../go/testdata/tokens/alternation.yaml | 9 +++++++ .../go/testdata/tokens/empty-string.yaml | 6 +++++ .../tokens/escape-non-reserved-character.yaml | 8 ++++++ .../testdata/tokens/escaped-alternation.yaml | 9 +++++++ ...ed-char-has-start-index-of-text-token.yaml | 9 +++++++ .../testdata/tokens/escaped-end-of-line.yaml | 8 ++++++ .../go/testdata/tokens/escaped-optional.yaml | 7 +++++ .../go/testdata/tokens/escaped-parameter.yaml | 7 +++++ .../go/testdata/tokens/escaped-space.yaml | 7 +++++ .../go/testdata/tokens/optional-phrase.yaml | 13 +++++++++ .../go/testdata/tokens/optional.yaml | 9 +++++++ .../go/testdata/tokens/parameter-phrase.yaml | 13 +++++++++ .../go/testdata/tokens/parameter.yaml | 9 +++++++ .../go/testdata/tokens/phrase.yaml | 11 ++++++++ 89 files changed, 640 insertions(+) create mode 100644 cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation.yaml create mode 100644 cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/ast/closing-brace.yaml create mode 100644 cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml create mode 100644 cucumber-expressions/go/testdata/ast/empty-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/ast/empty-alternations.yaml create mode 100644 cucumber-expressions/go/testdata/ast/empty-string.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-backslash.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/opening-brace.yaml create mode 100644 cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml create mode 100644 cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/optional-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/ast/optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/parameter.yaml create mode 100644 cucumber-expressions/go/testdata/ast/phrase.yaml create mode 100644 cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml create mode 100644 cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-float-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-float-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-int.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-word.yaml create mode 100644 cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/alternation.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/empty-string.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-optional.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-space.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/optional-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/optional.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/parameter.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/phrase.yaml diff --git a/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml new file mode 100644 index 0000000000..0bfb53a534 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml @@ -0,0 +1,17 @@ +expression: three blind\ rat/cat(s) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 16, "token": "blind rat"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 17, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 17, "end": 20, "token": "cat"}, + {"type": "OPTIONAL_NODE", "start": 20, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": "s"} + ]} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml new file mode 100644 index 0000000000..9a243822d5 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml @@ -0,0 +1,16 @@ +expression: three hungry/blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 12, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 12, "token": "hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 13, "end": 18, "token": "blind"} + ]} + ]}, + {"type": "TEXT_NODE", "start": 18, "end": 19, "token": " "}, + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml new file mode 100644 index 0000000000..c5daf32bd7 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml @@ -0,0 +1,27 @@ +expression: I select the {int}st/nd/rd/th +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "I"}, + {"type": "TEXT_NODE", "start": 1, "end": 2, "token": " "}, + {"type": "TEXT_NODE", "start": 2, "end": 8, "token": "select"}, + {"type": "TEXT_NODE", "start": 8, "end": 9, "token": " "}, + {"type": "TEXT_NODE", "start": 9, "end": 12, "token": "the"}, + {"type": "TEXT_NODE", "start": 12, "end": 13, "token": " "}, + {"type": "PARAMETER_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 14, "end": 17, "token": "int"} + ]}, + {"type": "ALTERNATION_NODE", "start": 18, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 18, "end": 20, "nodes": [ + {"type": "TEXT_NODE", "start": 18, "end": 20, "token": "st"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 21, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 23, "token": "nd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 24, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 24, "end": 26, "token": "rd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 27, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 27, "end": 29, "token": "th"} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml new file mode 100644 index 0000000000..842838b75f --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml @@ -0,0 +1,15 @@ +expression: three )blind\ mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 7, "token": ")"}, + {"type": "TEXT_NODE", "start": 7, "end": 18, "token": "blind mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 19, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml new file mode 100644 index 0000000000..e2f0584556 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml @@ -0,0 +1,8 @@ +expression: three blind\ mice/rats( +exception: |- + This Cucumber Expression has a problem at column 23: + + three blind\ mice/rats( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml new file mode 100644 index 0000000000..eedd57dd21 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml @@ -0,0 +1,12 @@ +expression: '\ three\ hungry/blind\ mice\ ' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 15, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 15, "token": " three hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 16, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 16, "end": 29, "token": "blind mice "} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation.yaml b/cucumber-expressions/go/testdata/ast/alternation.yaml new file mode 100644 index 0000000000..88df8325fe --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation.yaml @@ -0,0 +1,12 @@ +expression: mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 4, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 4, "token": "mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 5, "end": 9, "nodes": [ + {"type": "TEXT_NODE", "start": 5, "end": 9, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml new file mode 100644 index 0000000000..2c4d339333 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml @@ -0,0 +1,5 @@ +expression: "{}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 2, "nodes": []} + ]} diff --git a/cucumber-expressions/go/testdata/ast/closing-brace.yaml b/cucumber-expressions/go/testdata/ast/closing-brace.yaml new file mode 100644 index 0000000000..1bafd9c6a8 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/closing-brace.yaml @@ -0,0 +1,5 @@ +expression: "}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "}"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml new file mode 100644 index 0000000000..23daf7bcd3 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: ) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": ")"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-alternation.yaml b/cucumber-expressions/go/testdata/ast/empty-alternation.yaml new file mode 100644 index 0000000000..6d810fc8f3 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/empty-alternation.yaml @@ -0,0 +1,8 @@ +expression: / +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-alternations.yaml b/cucumber-expressions/go/testdata/ast/empty-alternations.yaml new file mode 100644 index 0000000000..f8d4dd4cf8 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/empty-alternations.yaml @@ -0,0 +1,9 @@ +expression: '//' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-string.yaml b/cucumber-expressions/go/testdata/ast/empty-string.yaml new file mode 100644 index 0000000000..4d33c2dc76 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/empty-string.yaml @@ -0,0 +1,3 @@ +expression: "" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 0, "nodes": []} diff --git a/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml new file mode 100644 index 0000000000..3ed9c37674 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml @@ -0,0 +1,5 @@ +expression: 'mice\/rats' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 10, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 10, "token": "mice/rats"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml new file mode 100644 index 0000000000..da2d008e1e --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml @@ -0,0 +1,5 @@ +expression: '\\' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\\"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml new file mode 100644 index 0000000000..afafc59eb8 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: '\(' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "("} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml new file mode 100644 index 0000000000..1e4746291b --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -0,0 +1,15 @@ +expression: three \((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 8, "token": "("}, + {"type": "OPTIONAL_NODE", "start": 8, "end": 14, "nodes": [ + {"type": "TEXT_NODE", "start": 9, "end": 13, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": ")"}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml new file mode 100644 index 0000000000..832249e2a7 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml @@ -0,0 +1,10 @@ +expression: three \(blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 19, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 13, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": ")"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 19, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional.yaml new file mode 100644 index 0000000000..4c2b457d6f --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: '\(blind)' + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 7, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": ")"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/opening-brace.yaml b/cucumber-expressions/go/testdata/ast/opening-brace.yaml new file mode 100644 index 0000000000..916a674a36 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/opening-brace.yaml @@ -0,0 +1,8 @@ +expression: '{' +exception: |- + This Cucumber Expression has a problem at column 1: + + { + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml new file mode 100644 index 0000000000..929d6ae304 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml @@ -0,0 +1,8 @@ +expression: ( +exception: |- + This Cucumber Expression has a problem at column 1: + + ( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml new file mode 100644 index 0000000000..f09199a454 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml @@ -0,0 +1,14 @@ +expression: three ((very\) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, + {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/optional-phrase.yaml b/cucumber-expressions/go/testdata/ast/optional-phrase.yaml new file mode 100644 index 0000000000..0ef4fb3f95 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/optional-phrase.yaml @@ -0,0 +1,12 @@ +expression: three (blind) mice + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 12, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 18, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/optional.yaml b/cucumber-expressions/go/testdata/ast/optional.yaml new file mode 100644 index 0000000000..6ce2b632e7 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/optional.yaml @@ -0,0 +1,7 @@ +expression: (blind) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 6, "token": "blind"} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/parameter.yaml b/cucumber-expressions/go/testdata/ast/parameter.yaml new file mode 100644 index 0000000000..92ac8c147e --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/parameter.yaml @@ -0,0 +1,7 @@ +expression: "{string}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 7, "token": "string"} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/phrase.yaml b/cucumber-expressions/go/testdata/ast/phrase.yaml new file mode 100644 index 0000000000..ba340d0122 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/phrase.yaml @@ -0,0 +1,9 @@ +expression: three blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 11, "token": "blind"}, + {"type": "TEXT_NODE", "start": 11, "end": 12, "token": " "}, + {"type": "TEXT_NODE", "start": 12, "end": 16, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml new file mode 100644 index 0000000000..d02f9b4ccf --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml @@ -0,0 +1,8 @@ +expression: "{string" +exception: |- + This Cucumber Expression has a problem at column 1: + + {string + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml b/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml new file mode 100644 index 0000000000..a00b45acef --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml @@ -0,0 +1,4 @@ +expression: \({int}) +text: (3) +expected: |- + [3] diff --git a/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml new file mode 100644 index 0000000000..bb1a6f21b1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: i18n +expected: |- + [18] diff --git a/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml new file mode 100644 index 0000000000..cdddce7d84 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: a11y +expected: |- + [11] diff --git a/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml b/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml new file mode 100644 index 0000000000..9e2ecdfbe1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml @@ -0,0 +1,5 @@ +expression: |- + {int}st/nd/rd/th +text: 3rd +expected: |- + [3] diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml new file mode 100644 index 0000000000..b32540a4a9 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml @@ -0,0 +1,10 @@ +expression: |- + {int}/x +text: '3' +exception: |- + This Cucumber Expression has a problem at column 6: + + {int}/x + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml new file mode 100644 index 0000000000..a0aab0e5a9 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml @@ -0,0 +1,9 @@ +expression: three (brown)/black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (brown)/black mice + ^-----^ + An alternative may not exclusively contain optionals. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml new file mode 100644 index 0000000000..50250f00aa --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml @@ -0,0 +1,9 @@ +expression: x/{int} +text: '3' +exception: |- + This Cucumber Expression has a problem at column 3: + + x/{int} + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml new file mode 100644 index 0000000000..b724cfa77f --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml @@ -0,0 +1,9 @@ +expression: three brown//black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three brown//black mice + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml new file mode 100644 index 0000000000..00e341af0b --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml @@ -0,0 +1,9 @@ +expression: three () mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three () mice + ^^ + An optional must contain some text. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml new file mode 100644 index 0000000000..b88061e9b4 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml @@ -0,0 +1,9 @@ +expression: ({int}) +text: '3' +exception: |- + This Cucumber Expression has a problem at column 2: + + ({int}) + ^---^ + An optional may not contain a parameter type. + If you did not mean to use an parameter type you can use '\{' to escape the the '{' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml new file mode 100644 index 0000000000..1dd65aa276 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml @@ -0,0 +1,10 @@ +expression: |- + {[string]} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {[string]} + ^--------^ + Parameter names may not contain '[]()$.|?*+'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml b/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml new file mode 100644 index 0000000000..18023180af --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind' mice +expected: |- + null diff --git a/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml b/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml new file mode 100644 index 0000000000..d66b586430 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '1.22' +expected: |- + null diff --git a/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml new file mode 100644 index 0000000000..15fe78bf53 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml @@ -0,0 +1,4 @@ +expression: three( brown/black) mice +text: three brown/black mice +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-alternation.yaml b/cucumber-expressions/go/testdata/expression/matches-alternation.yaml new file mode 100644 index 0000000000..20a9b9a728 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-alternation.yaml @@ -0,0 +1,4 @@ +expression: mice/rats and rats\/mice +text: rats and rats/mice +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml b/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml new file mode 100644 index 0000000000..fc954960df --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml @@ -0,0 +1,5 @@ +expression: |- + {} +text: '0.22' +expected: |- + ["0.22"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c3e1962e22 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "" and "handsome" mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..89315b62ef --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "" mice +expected: |- + [""] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml new file mode 100644 index 0000000000..fe30a044c1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "bl\"nd" mice +expected: |- + ["bl\"nd"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml new file mode 100644 index 0000000000..25fcf304c1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "'blind'" mice +expected: |- + ["'blind'"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml new file mode 100644 index 0000000000..8b0560f332 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml new file mode 100644 index 0000000000..902a084103 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml @@ -0,0 +1,4 @@ +expression: three \\(exceptionally) \\{string} mice +text: three \exceptionally \"blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml new file mode 100644 index 0000000000..94e531eca7 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: 12\ +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml new file mode 100644 index 0000000000..9c9c735b86 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: '2020' +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml new file mode 100644 index 0000000000..171df89ee1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml @@ -0,0 +1,4 @@ +expression: three \(exceptionally) \{string} mice +text: three (exceptionally) {string} mice +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml new file mode 100644 index 0000000000..d8b3933399 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml @@ -0,0 +1,4 @@ +expression: 12\/2020 +text: 12/2020 +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-float-1.yaml b/cucumber-expressions/go/testdata/expression/matches-float-1.yaml new file mode 100644 index 0000000000..fe7e8b1869 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-float-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '0.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/go/testdata/expression/matches-float-2.yaml b/cucumber-expressions/go/testdata/expression/matches-float-2.yaml new file mode 100644 index 0000000000..c1e5894eac --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-float-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/go/testdata/expression/matches-int.yaml b/cucumber-expressions/go/testdata/expression/matches-int.yaml new file mode 100644 index 0000000000..bcd3763886 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '22' +expected: |- + [22] diff --git a/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml new file mode 100644 index 0000000000..6c74bc2350 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "blind" and "crippled" mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml new file mode 100644 index 0000000000..5037821c14 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three 'blind' and 'crippled' mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml new file mode 100644 index 0000000000..821776715b --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three brown mice +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml new file mode 100644 index 0000000000..71b3a341f1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three rats +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml new file mode 100644 index 0000000000..2632f410ce --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 2 seconds? +expected: |- + [2] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml new file mode 100644 index 0000000000..7b30f667bc --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 1 second. +expected: |- + [1] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml new file mode 100644 index 0000000000..6574bb4bdf --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 3 rats +expected: |- + [3] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml new file mode 100644 index 0000000000..4eb0f0e1c6 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 2 mice +expected: |- + [2] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml new file mode 100644 index 0000000000..964fa6489e --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 1 mouse +expected: |- + [1] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c963dcf1c7 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three '' and 'handsome' mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..cff2a2d1ec --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '' mice +expected: |- + [""] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml new file mode 100644 index 0000000000..eb9ed537cd --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '"blind"' mice +expected: |- + ["\"blind\""] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml new file mode 100644 index 0000000000..4c2b0055b9 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'bl\'nd' mice +expected: |- + ["bl'nd"] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml new file mode 100644 index 0000000000..6c8f4652a5 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'blind' mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-word.yaml b/cucumber-expressions/go/testdata/expression/matches-word.yaml new file mode 100644 index 0000000000..358fd3afd1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-word.yaml @@ -0,0 +1,4 @@ +expression: three {word} mice +text: three blind mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml b/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml new file mode 100644 index 0000000000..384e3a48c3 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml @@ -0,0 +1,10 @@ +expression: |- + {unknown} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {unknown} + ^-------^ + Undefined parameter type 'unknown'. + Please register a ParameterType for 'unknown' diff --git a/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml new file mode 100644 index 0000000000..48b107f64e --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml @@ -0,0 +1,13 @@ +expression: three blind/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "ALTERNATION", "start": 11, "end": 12, "text": "/"}, + {"type": "TEXT", "start": 12, "end": 19, "text": "cripple"}, + {"type": "WHITE_SPACE", "start": 19, "end": 20, "text": " "}, + {"type": "TEXT", "start": 20, "end": 24, "text": "mice"}, + {"type": "END_OF_LINE", "start": 24, "end": 24, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/alternation.yaml b/cucumber-expressions/go/testdata/tokens/alternation.yaml new file mode 100644 index 0000000000..a4920f22e5 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/alternation.yaml @@ -0,0 +1,9 @@ +expression: blind/cripple +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "blind"}, + {"type": "ALTERNATION", "start": 5, "end": 6, "text": "/"}, + {"type": "TEXT", "start": 6, "end": 13, "text": "cripple"}, + {"type": "END_OF_LINE", "start": 13, "end": 13, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/empty-string.yaml b/cucumber-expressions/go/testdata/tokens/empty-string.yaml new file mode 100644 index 0000000000..501f7522f2 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/empty-string.yaml @@ -0,0 +1,6 @@ +expression: "" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "END_OF_LINE", "start": 0, "end": 0, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml new file mode 100644 index 0000000000..5e206be084 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml @@ -0,0 +1,8 @@ +expression: \[ +exception: |- + This Cucumber Expression has a problem at column 2: + + \[ + ^ + Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. + If you did mean to use an '\' you can use '\\' to escape it diff --git a/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml new file mode 100644 index 0000000000..7e21f7ad19 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml @@ -0,0 +1,9 @@ +expression: blind\ and\ famished\/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 29, "text": "blind and famished/cripple"}, + {"type": "WHITE_SPACE", "start": 29, "end": 30, "text": " "}, + {"type": "TEXT", "start": 30, "end": 34, "text": "mice"}, + {"type": "END_OF_LINE", "start": 34, "end": 34, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml b/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml new file mode 100644 index 0000000000..6375ad52a5 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml @@ -0,0 +1,9 @@ +expression: ' \/ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "WHITE_SPACE", "start": 0, "end": 1, "text": " "}, + {"type": "TEXT", "start": 1, "end": 3, "text": "/"}, + {"type": "WHITE_SPACE", "start": 3, "end": 4, "text": " "}, + {"type": "END_OF_LINE", "start": 4, "end": 4, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml new file mode 100644 index 0000000000..a1bd00fd98 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml @@ -0,0 +1,8 @@ +expression: \ +exception: |- + This Cucumber Expression has a problem at column 1: + + \ + ^ + The end of line can not be escaped. + You can use '\\' to escape the the '\' diff --git a/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml new file mode 100644 index 0000000000..2b365b581c --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: \(blind\) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 9, "text": "(blind)"}, + {"type": "END_OF_LINE", "start": 9, "end": 9, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml new file mode 100644 index 0000000000..2cdac4f35a --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml @@ -0,0 +1,7 @@ +expression: \{string\} +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 10, "text": "{string}"}, + {"type": "END_OF_LINE", "start": 10, "end": 10, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-space.yaml b/cucumber-expressions/go/testdata/tokens/escaped-space.yaml new file mode 100644 index 0000000000..912827a941 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-space.yaml @@ -0,0 +1,7 @@ +expression: '\ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 2, "text": " "}, + {"type": "END_OF_LINE", "start": 2, "end": 2, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml new file mode 100644 index 0000000000..2ddc6bb502 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml @@ -0,0 +1,13 @@ +expression: three (blind) mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_OPTIONAL", "start": 6, "end": 7, "text": "("}, + {"type": "TEXT", "start": 7, "end": 12, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 12, "end": 13, "text": ")"}, + {"type": "WHITE_SPACE", "start": 13, "end": 14, "text": " "}, + {"type": "TEXT", "start": 14, "end": 18, "text": "mice"}, + {"type": "END_OF_LINE", "start": 18, "end": 18, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/optional.yaml b/cucumber-expressions/go/testdata/tokens/optional.yaml new file mode 100644 index 0000000000..35b1474a7c --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/optional.yaml @@ -0,0 +1,9 @@ +expression: (blind) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_OPTIONAL", "start": 0, "end": 1, "text": "("}, + {"type": "TEXT", "start": 1, "end": 6, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 6, "end": 7, "text": ")"}, + {"type": "END_OF_LINE", "start": 7, "end": 7, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml new file mode 100644 index 0000000000..5e98055ee6 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml @@ -0,0 +1,13 @@ +expression: three {string} mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_PARAMETER", "start": 6, "end": 7, "text": "{"}, + {"type": "TEXT", "start": 7, "end": 13, "text": "string"}, + {"type": "END_PARAMETER", "start": 13, "end": 14, "text": "}"}, + {"type": "WHITE_SPACE", "start": 14, "end": 15, "text": " "}, + {"type": "TEXT", "start": 15, "end": 19, "text": "mice"}, + {"type": "END_OF_LINE", "start": 19, "end": 19, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/parameter.yaml b/cucumber-expressions/go/testdata/tokens/parameter.yaml new file mode 100644 index 0000000000..460363c393 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/parameter.yaml @@ -0,0 +1,9 @@ +expression: "{string}" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_PARAMETER", "start": 0, "end": 1, "text": "{"}, + {"type": "TEXT", "start": 1, "end": 7, "text": "string"}, + {"type": "END_PARAMETER", "start": 7, "end": 8, "text": "}"}, + {"type": "END_OF_LINE", "start": 8, "end": 8, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/phrase.yaml b/cucumber-expressions/go/testdata/tokens/phrase.yaml new file mode 100644 index 0000000000..e2cfccf7b4 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/phrase.yaml @@ -0,0 +1,11 @@ +expression: three blind mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "WHITE_SPACE", "start": 11, "end": 12, "text": " "}, + {"type": "TEXT", "start": 12, "end": 16, "text": "mice"}, + {"type": "END_OF_LINE", "start": 16, "end": 16, "text": ""} + ] From 00495e0627e9860d4d37ad8ea76f1189811450d1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 19 Sep 2020 12:18:57 +0200 Subject: [PATCH 138/183] Don't make test data --- cucumber-expressions/Makefile | 1 + .../ast/alternation-followed-by-optional.yaml | 17 ------------ .../go/testdata/ast/alternation-phrase.yaml | 16 ----------- .../ast/alternation-with-parameter.yaml | 27 ------------------- .../alternation-with-unused-end-optional.yaml | 15 ----------- ...lternation-with-unused-start-optional.yaml | 8 ------ .../ast/alternation-with-white-space.yaml | 12 --------- .../go/testdata/ast/alternation.yaml | 12 --------- .../go/testdata/ast/anonymous-parameter.yaml | 5 ---- .../go/testdata/ast/closing-brace.yaml | 5 ---- .../go/testdata/ast/closing-parenthesis.yaml | 5 ---- .../go/testdata/ast/empty-alternation.yaml | 8 ------ .../go/testdata/ast/empty-alternations.yaml | 9 ------- .../go/testdata/ast/empty-string.yaml | 3 --- .../go/testdata/ast/escaped-alternation.yaml | 5 ---- .../go/testdata/ast/escaped-backslash.yaml | 5 ---- .../ast/escaped-opening-parenthesis.yaml | 5 ---- ...escaped-optional-followed-by-optional.yaml | 15 ----------- .../testdata/ast/escaped-optional-phrase.yaml | 10 ------- .../go/testdata/ast/escaped-optional.yaml | 7 ----- .../go/testdata/ast/opening-brace.yaml | 8 ------ .../go/testdata/ast/opening-parenthesis.yaml | 8 ------ .../optional-containing-escaped-optional.yaml | 14 ---------- .../go/testdata/ast/optional-phrase.yaml | 12 --------- .../go/testdata/ast/optional.yaml | 7 ----- .../go/testdata/ast/parameter.yaml | 7 ----- .../go/testdata/ast/phrase.yaml | 9 ------- .../go/testdata/ast/unfinished-parameter.yaml | 8 ------ ...lows-escaped-optional-parameter-types.yaml | 4 --- ...llows-parameter-type-in-alternation-1.yaml | 4 --- ...llows-parameter-type-in-alternation-2.yaml | 4 --- ...low-parameter-adjacent-to-alternation.yaml | 5 ---- ...lternative-by-adjacent-left-parameter.yaml | 10 ------- ...mpty-alternative-by-adjacent-optional.yaml | 9 ------- ...ternative-by-adjacent-right-parameter.yaml | 9 ------- ...ow-alternation-with-empty-alternative.yaml | 9 ------- .../does-not-allow-empty-optional.yaml | 9 ------- ...es-not-allow-optional-parameter-types.yaml | 9 ------- ...llow-parameter-type-with-left-bracket.yaml | 10 ------- .../does-not-match-misquoted-string.yaml | 4 --- .../expression/doesnt-match-float-as-int.yaml | 5 ---- ...tches-alternation-in-optional-as-text.yaml | 4 --- .../expression/matches-alternation.yaml | 4 --- .../matches-anonymous-parameter-type.yaml | 5 ---- ...empty-string-along-with-other-strings.yaml | 4 --- ...e-quoted-empty-string-as-empty-string.yaml | 4 --- ...oted-string-with-escaped-double-quote.yaml | 4 --- ...uble-quoted-string-with-single-quotes.yaml | 4 --- .../matches-double-quoted-string.yaml | 4 --- .../matches-doubly-escaped-parenthesis.yaml | 4 --- .../matches-doubly-escaped-slash-1.yaml | 4 --- .../matches-doubly-escaped-slash-2.yaml | 4 --- .../matches-escaped-parenthesis-1.yaml | 4 --- .../matches-escaped-parenthesis-2.yaml | 4 --- .../matches-escaped-parenthesis-3.yaml | 4 --- .../expression/matches-escaped-slash.yaml | 4 --- .../testdata/expression/matches-float-1.yaml | 5 ---- .../testdata/expression/matches-float-2.yaml | 5 ---- .../go/testdata/expression/matches-int.yaml | 5 ---- ...atches-multiple-double-quoted-strings.yaml | 4 --- ...atches-multiple-single-quoted-strings.yaml | 4 --- ...matches-optional-before-alternation-1.yaml | 4 --- ...matches-optional-before-alternation-2.yaml | 4 --- ...e-alternation-with-regex-characters-1.yaml | 4 --- ...e-alternation-with-regex-characters-2.yaml | 4 --- .../matches-optional-in-alternation-1.yaml | 5 ---- .../matches-optional-in-alternation-2.yaml | 5 ---- .../matches-optional-in-alternation-3.yaml | 5 ---- ...empty-string-along-with-other-strings.yaml | 4 --- ...e-quoted-empty-string-as-empty-string.yaml | 4 --- ...ngle-quoted-string-with-double-quotes.yaml | 4 --- ...oted-string-with-escaped-single-quote.yaml | 4 --- .../matches-single-quoted-string.yaml | 4 --- .../go/testdata/expression/matches-word.yaml | 4 --- .../throws-unknown-parameter-type.yaml | 10 ------- .../testdata/tokens/alternation-phrase.yaml | 13 --------- .../go/testdata/tokens/alternation.yaml | 9 ------- .../go/testdata/tokens/empty-string.yaml | 6 ----- .../tokens/escape-non-reserved-character.yaml | 8 ------ .../testdata/tokens/escaped-alternation.yaml | 9 ------- ...ed-char-has-start-index-of-text-token.yaml | 9 ------- .../testdata/tokens/escaped-end-of-line.yaml | 8 ------ .../go/testdata/tokens/escaped-optional.yaml | 7 ----- .../go/testdata/tokens/escaped-parameter.yaml | 7 ----- .../go/testdata/tokens/escaped-space.yaml | 7 ----- .../go/testdata/tokens/optional-phrase.yaml | 13 --------- .../go/testdata/tokens/optional.yaml | 9 ------- .../go/testdata/tokens/parameter-phrase.yaml | 13 --------- .../go/testdata/tokens/parameter.yaml | 9 ------- .../go/testdata/tokens/phrase.yaml | 11 -------- 90 files changed, 1 insertion(+), 640 deletions(-) delete mode 100644 cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/alternation-phrase.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/alternation.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/closing-brace.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/empty-alternation.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/empty-alternations.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/empty-string.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/escaped-alternation.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/escaped-backslash.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/opening-brace.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/optional-phrase.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/optional.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/parameter.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/phrase.yaml delete mode 100644 cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-alternation.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-float-1.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-float-2.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-int.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-word.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/alternation.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/empty-string.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/escaped-optional.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/escaped-space.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/optional-phrase.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/optional.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/parameter.yaml delete mode 100644 cucumber-expressions/go/testdata/tokens/phrase.yaml diff --git a/cucumber-expressions/Makefile b/cucumber-expressions/Makefile index 551e68e27a..3e7523e10c 100644 --- a/cucumber-expressions/Makefile +++ b/cucumber-expressions/Makefile @@ -1 +1,2 @@ +LANGUAGES ?= go javascript ruby java include default.mk diff --git a/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml deleted file mode 100644 index 0bfb53a534..0000000000 --- a/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml +++ /dev/null @@ -1,17 +0,0 @@ -expression: three blind\ rat/cat(s) -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 6, "end": 16, "nodes": [ - {"type": "TEXT_NODE", "start": 6, "end": 16, "token": "blind rat"} - ]}, - {"type": "ALTERNATIVE_NODE", "start": 17, "end": 23, "nodes": [ - {"type": "TEXT_NODE", "start": 17, "end": 20, "token": "cat"}, - {"type": "OPTIONAL_NODE", "start": 20, "end": 23, "nodes": [ - {"type": "TEXT_NODE", "start": 21, "end": 22, "token": "s"} - ]} - ]} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml deleted file mode 100644 index 9a243822d5..0000000000 --- a/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml +++ /dev/null @@ -1,16 +0,0 @@ -expression: three hungry/blind mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "ALTERNATION_NODE", "start": 6, "end": 18, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 6, "end": 12, "nodes": [ - {"type": "TEXT_NODE", "start": 6, "end": 12, "token": "hungry"} - ]}, - {"type": "ALTERNATIVE_NODE", "start": 13, "end": 18, "nodes": [ - {"type": "TEXT_NODE", "start": 13, "end": 18, "token": "blind"} - ]} - ]}, - {"type": "TEXT_NODE", "start": 18, "end": 19, "token": " "}, - {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "mice"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml deleted file mode 100644 index c5daf32bd7..0000000000 --- a/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml +++ /dev/null @@ -1,27 +0,0 @@ -expression: I select the {int}st/nd/rd/th -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "I"}, - {"type": "TEXT_NODE", "start": 1, "end": 2, "token": " "}, - {"type": "TEXT_NODE", "start": 2, "end": 8, "token": "select"}, - {"type": "TEXT_NODE", "start": 8, "end": 9, "token": " "}, - {"type": "TEXT_NODE", "start": 9, "end": 12, "token": "the"}, - {"type": "TEXT_NODE", "start": 12, "end": 13, "token": " "}, - {"type": "PARAMETER_NODE", "start": 13, "end": 18, "nodes": [ - {"type": "TEXT_NODE", "start": 14, "end": 17, "token": "int"} - ]}, - {"type": "ALTERNATION_NODE", "start": 18, "end": 29, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 18, "end": 20, "nodes": [ - {"type": "TEXT_NODE", "start": 18, "end": 20, "token": "st"} - ]}, - {"type": "ALTERNATIVE_NODE", "start": 21, "end": 23, "nodes": [ - {"type": "TEXT_NODE", "start": 21, "end": 23, "token": "nd"} - ]}, - {"type": "ALTERNATIVE_NODE", "start": 24, "end": 26, "nodes": [ - {"type": "TEXT_NODE", "start": 24, "end": 26, "token": "rd"} - ]}, - {"type": "ALTERNATIVE_NODE", "start": 27, "end": 29, "nodes": [ - {"type": "TEXT_NODE", "start": 27, "end": 29, "token": "th"} - ]} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml deleted file mode 100644 index 842838b75f..0000000000 --- a/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml +++ /dev/null @@ -1,15 +0,0 @@ -expression: three )blind\ mice/rats -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 6, "end": 18, "nodes": [ - {"type": "TEXT_NODE", "start": 6, "end": 7, "token": ")"}, - {"type": "TEXT_NODE", "start": 7, "end": 18, "token": "blind mice"} - ]}, - {"type": "ALTERNATIVE_NODE", "start": 19, "end": 23, "nodes": [ - {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "rats"} - ]} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml deleted file mode 100644 index e2f0584556..0000000000 --- a/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml +++ /dev/null @@ -1,8 +0,0 @@ -expression: three blind\ mice/rats( -exception: |- - This Cucumber Expression has a problem at column 23: - - three blind\ mice/rats( - ^ - The '(' does not have a matching ')'. - If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml deleted file mode 100644 index eedd57dd21..0000000000 --- a/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml +++ /dev/null @@ -1,12 +0,0 @@ -expression: '\ three\ hungry/blind\ mice\ ' -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ - {"type": "ALTERNATION_NODE", "start": 0, "end": 29, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 0, "end": 15, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 15, "token": " three hungry"} - ]}, - {"type": "ALTERNATIVE_NODE", "start": 16, "end": 29, "nodes": [ - {"type": "TEXT_NODE", "start": 16, "end": 29, "token": "blind mice "} - ]} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation.yaml b/cucumber-expressions/go/testdata/ast/alternation.yaml deleted file mode 100644 index 88df8325fe..0000000000 --- a/cucumber-expressions/go/testdata/ast/alternation.yaml +++ /dev/null @@ -1,12 +0,0 @@ -expression: mice/rats -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 9, "nodes": [ - {"type": "ALTERNATION_NODE", "start": 0, "end": 9, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 0, "end": 4, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 4, "token": "mice"} - ]}, - {"type": "ALTERNATIVE_NODE", "start": 5, "end": 9, "nodes": [ - {"type": "TEXT_NODE", "start": 5, "end": 9, "token": "rats"} - ]} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml deleted file mode 100644 index 2c4d339333..0000000000 --- a/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: "{}" -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ - {"type": "PARAMETER_NODE", "start": 0, "end": 2, "nodes": []} - ]} diff --git a/cucumber-expressions/go/testdata/ast/closing-brace.yaml b/cucumber-expressions/go/testdata/ast/closing-brace.yaml deleted file mode 100644 index 1bafd9c6a8..0000000000 --- a/cucumber-expressions/go/testdata/ast/closing-brace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: "}" -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "}"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml deleted file mode 100644 index 23daf7bcd3..0000000000 --- a/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: ) -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 1, "token": ")"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-alternation.yaml b/cucumber-expressions/go/testdata/ast/empty-alternation.yaml deleted file mode 100644 index 6d810fc8f3..0000000000 --- a/cucumber-expressions/go/testdata/ast/empty-alternation.yaml +++ /dev/null @@ -1,8 +0,0 @@ -expression: / -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ - {"type": "ALTERNATION_NODE", "start": 0, "end": 1, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, - {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-alternations.yaml b/cucumber-expressions/go/testdata/ast/empty-alternations.yaml deleted file mode 100644 index f8d4dd4cf8..0000000000 --- a/cucumber-expressions/go/testdata/ast/empty-alternations.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: '//' -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ - {"type": "ALTERNATION_NODE", "start": 0, "end": 2, "nodes": [ - {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, - {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []}, - {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2, "nodes": []} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-string.yaml b/cucumber-expressions/go/testdata/ast/empty-string.yaml deleted file mode 100644 index 4d33c2dc76..0000000000 --- a/cucumber-expressions/go/testdata/ast/empty-string.yaml +++ /dev/null @@ -1,3 +0,0 @@ -expression: "" -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 0, "nodes": []} diff --git a/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml deleted file mode 100644 index 3ed9c37674..0000000000 --- a/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: 'mice\/rats' -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 10, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 10, "token": "mice/rats"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml deleted file mode 100644 index da2d008e1e..0000000000 --- a/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: '\\' -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\\"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml deleted file mode 100644 index afafc59eb8..0000000000 --- a/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: '\(' -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "("} - ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml deleted file mode 100644 index 1e4746291b..0000000000 --- a/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml +++ /dev/null @@ -1,15 +0,0 @@ -expression: three \((very) blind) mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "TEXT_NODE", "start": 6, "end": 8, "token": "("}, - {"type": "OPTIONAL_NODE", "start": 8, "end": 14, "nodes": [ - {"type": "TEXT_NODE", "start": 9, "end": 13, "token": "very"} - ]}, - {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, - {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"}, - {"type": "TEXT_NODE", "start": 20, "end": 21, "token": ")"}, - {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, - {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml deleted file mode 100644 index 832249e2a7..0000000000 --- a/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: three \(blind) mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 19, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "TEXT_NODE", "start": 6, "end": 13, "token": "(blind"}, - {"type": "TEXT_NODE", "start": 13, "end": 14, "token": ")"}, - {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, - {"type": "TEXT_NODE", "start": 15, "end": 19, "token": "mice"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional.yaml deleted file mode 100644 index 4c2b457d6f..0000000000 --- a/cucumber-expressions/go/testdata/ast/escaped-optional.yaml +++ /dev/null @@ -1,7 +0,0 @@ -expression: '\(blind)' - -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 7, "token": "(blind"}, - {"type": "TEXT_NODE", "start": 7, "end": 8, "token": ")"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/opening-brace.yaml b/cucumber-expressions/go/testdata/ast/opening-brace.yaml deleted file mode 100644 index 916a674a36..0000000000 --- a/cucumber-expressions/go/testdata/ast/opening-brace.yaml +++ /dev/null @@ -1,8 +0,0 @@ -expression: '{' -exception: |- - This Cucumber Expression has a problem at column 1: - - { - ^ - The '{' does not have a matching '}'. - If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml deleted file mode 100644 index 929d6ae304..0000000000 --- a/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml +++ /dev/null @@ -1,8 +0,0 @@ -expression: ( -exception: |- - This Cucumber Expression has a problem at column 1: - - ( - ^ - The '(' does not have a matching ')'. - If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml deleted file mode 100644 index f09199a454..0000000000 --- a/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml +++ /dev/null @@ -1,14 +0,0 @@ -expression: three ((very\) blind) mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ - {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, - {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, - {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, - {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} - ]}, - {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, - {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/optional-phrase.yaml b/cucumber-expressions/go/testdata/ast/optional-phrase.yaml deleted file mode 100644 index 0ef4fb3f95..0000000000 --- a/cucumber-expressions/go/testdata/ast/optional-phrase.yaml +++ /dev/null @@ -1,12 +0,0 @@ -expression: three (blind) mice - -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 18, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "OPTIONAL_NODE", "start": 6, "end": 13, "nodes": [ - {"type": "TEXT_NODE", "start": 7, "end": 12, "token": "blind"} - ]}, - {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, - {"type": "TEXT_NODE", "start": 14, "end": 18, "token": "mice"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/optional.yaml b/cucumber-expressions/go/testdata/ast/optional.yaml deleted file mode 100644 index 6ce2b632e7..0000000000 --- a/cucumber-expressions/go/testdata/ast/optional.yaml +++ /dev/null @@ -1,7 +0,0 @@ -expression: (blind) -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 7, "nodes": [ - {"type": "OPTIONAL_NODE", "start": 0, "end": 7, "nodes": [ - {"type": "TEXT_NODE", "start": 1, "end": 6, "token": "blind"} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/parameter.yaml b/cucumber-expressions/go/testdata/ast/parameter.yaml deleted file mode 100644 index 92ac8c147e..0000000000 --- a/cucumber-expressions/go/testdata/ast/parameter.yaml +++ /dev/null @@ -1,7 +0,0 @@ -expression: "{string}" -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ - {"type": "PARAMETER_NODE", "start": 0, "end": 8, "nodes": [ - {"type": "TEXT_NODE", "start": 1, "end": 7, "token": "string"} - ]} - ]} diff --git a/cucumber-expressions/go/testdata/ast/phrase.yaml b/cucumber-expressions/go/testdata/ast/phrase.yaml deleted file mode 100644 index ba340d0122..0000000000 --- a/cucumber-expressions/go/testdata/ast/phrase.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: three blind mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 16, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "TEXT_NODE", "start": 6, "end": 11, "token": "blind"}, - {"type": "TEXT_NODE", "start": 11, "end": 12, "token": " "}, - {"type": "TEXT_NODE", "start": 12, "end": 16, "token": "mice"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml deleted file mode 100644 index d02f9b4ccf..0000000000 --- a/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml +++ /dev/null @@ -1,8 +0,0 @@ -expression: "{string" -exception: |- - This Cucumber Expression has a problem at column 1: - - {string - ^ - The '{' does not have a matching '}'. - If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml b/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml deleted file mode 100644 index a00b45acef..0000000000 --- a/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: \({int}) -text: (3) -expected: |- - [3] diff --git a/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml deleted file mode 100644 index bb1a6f21b1..0000000000 --- a/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: a/i{int}n/y -text: i18n -expected: |- - [18] diff --git a/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml deleted file mode 100644 index cdddce7d84..0000000000 --- a/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: a/i{int}n/y -text: a11y -expected: |- - [11] diff --git a/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml b/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml deleted file mode 100644 index 9e2ecdfbe1..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {int}st/nd/rd/th -text: 3rd -expected: |- - [3] diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml deleted file mode 100644 index b32540a4a9..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: |- - {int}/x -text: '3' -exception: |- - This Cucumber Expression has a problem at column 6: - - {int}/x - ^ - Alternative may not be empty. - If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml deleted file mode 100644 index a0aab0e5a9..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: three (brown)/black mice -text: three brown mice -exception: |- - This Cucumber Expression has a problem at column 7: - - three (brown)/black mice - ^-----^ - An alternative may not exclusively contain optionals. - If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml deleted file mode 100644 index 50250f00aa..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: x/{int} -text: '3' -exception: |- - This Cucumber Expression has a problem at column 3: - - x/{int} - ^ - Alternative may not be empty. - If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml deleted file mode 100644 index b724cfa77f..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: three brown//black mice -text: three brown mice -exception: |- - This Cucumber Expression has a problem at column 13: - - three brown//black mice - ^ - Alternative may not be empty. - If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml deleted file mode 100644 index 00e341af0b..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: three () mice -text: three brown mice -exception: |- - This Cucumber Expression has a problem at column 7: - - three () mice - ^^ - An optional must contain some text. - If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml deleted file mode 100644 index b88061e9b4..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: ({int}) -text: '3' -exception: |- - This Cucumber Expression has a problem at column 2: - - ({int}) - ^---^ - An optional may not contain a parameter type. - If you did not mean to use an parameter type you can use '\{' to escape the the '{' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml deleted file mode 100644 index 1dd65aa276..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: |- - {[string]} -text: something -exception: |- - This Cucumber Expression has a problem at column 1: - - {[string]} - ^--------^ - Parameter names may not contain '[]()$.|?*+'. - Did you mean to use a regular expression? diff --git a/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml b/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml deleted file mode 100644 index 18023180af..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three "blind' mice -expected: |- - null diff --git a/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml b/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml deleted file mode 100644 index d66b586430..0000000000 --- a/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {int} -text: '1.22' -expected: |- - null diff --git a/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml deleted file mode 100644 index 15fe78bf53..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three( brown/black) mice -text: three brown/black mice -expected: |- - [] diff --git a/cucumber-expressions/go/testdata/expression/matches-alternation.yaml b/cucumber-expressions/go/testdata/expression/matches-alternation.yaml deleted file mode 100644 index 20a9b9a728..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-alternation.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: mice/rats and rats\/mice -text: rats and rats/mice -expected: |- - [] diff --git a/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml b/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml deleted file mode 100644 index fc954960df..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {} -text: '0.22' -expected: |- - ["0.22"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml deleted file mode 100644 index c3e1962e22..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} and {string} mice -text: three "" and "handsome" mice -expected: |- - ["","handsome"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml deleted file mode 100644 index 89315b62ef..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three "" mice -expected: |- - [""] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml deleted file mode 100644 index fe30a044c1..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three "bl\"nd" mice -expected: |- - ["bl\"nd"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml deleted file mode 100644 index 25fcf304c1..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three "'blind'" mice -expected: |- - ["'blind'"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml deleted file mode 100644 index 8b0560f332..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three "blind" mice -expected: |- - ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml deleted file mode 100644 index 902a084103..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three \\(exceptionally) \\{string} mice -text: three \exceptionally \"blind" mice -expected: |- - ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml deleted file mode 100644 index 94e531eca7..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: 12\\/2020 -text: 12\ -expected: |- - [] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml deleted file mode 100644 index 9c9c735b86..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: 12\\/2020 -text: '2020' -expected: |- - [] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml deleted file mode 100644 index 171df89ee1..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three \(exceptionally) \{string} mice -text: three (exceptionally) {string} mice -expected: |- - [] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml deleted file mode 100644 index 340c63e94f..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three \((exceptionally)) \{{string}} mice -text: three (exceptionally) {"blind"} mice -expected: |- - ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml deleted file mode 100644 index 340c63e94f..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three \((exceptionally)) \{{string}} mice -text: three (exceptionally) {"blind"} mice -expected: |- - ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml deleted file mode 100644 index d8b3933399..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: 12\/2020 -text: 12/2020 -expected: |- - [] diff --git a/cucumber-expressions/go/testdata/expression/matches-float-1.yaml b/cucumber-expressions/go/testdata/expression/matches-float-1.yaml deleted file mode 100644 index fe7e8b1869..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-float-1.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {float} -text: '0.22' -expected: |- - [0.22] diff --git a/cucumber-expressions/go/testdata/expression/matches-float-2.yaml b/cucumber-expressions/go/testdata/expression/matches-float-2.yaml deleted file mode 100644 index c1e5894eac..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-float-2.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {float} -text: '.22' -expected: |- - [0.22] diff --git a/cucumber-expressions/go/testdata/expression/matches-int.yaml b/cucumber-expressions/go/testdata/expression/matches-int.yaml deleted file mode 100644 index bcd3763886..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-int.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {int} -text: '22' -expected: |- - [22] diff --git a/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml deleted file mode 100644 index 6c74bc2350..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} and {string} mice -text: three "blind" and "crippled" mice -expected: |- - ["blind","crippled"] diff --git a/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml deleted file mode 100644 index 5037821c14..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} and {string} mice -text: three 'blind' and 'crippled' mice -expected: |- - ["blind","crippled"] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml deleted file mode 100644 index 821776715b..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three (brown )mice/rats -text: three brown mice -expected: |- - [] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml deleted file mode 100644 index 71b3a341f1..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three (brown )mice/rats -text: three rats -expected: |- - [] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml deleted file mode 100644 index 2632f410ce..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: I wait {int} second(s)./second(s)? -text: I wait 2 seconds? -expected: |- - [2] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml deleted file mode 100644 index 7b30f667bc..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: I wait {int} second(s)./second(s)? -text: I wait 1 second. -expected: |- - [1] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml deleted file mode 100644 index 6574bb4bdf..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {int} rat(s)/mouse/mice -text: 3 rats -expected: |- - [3] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml deleted file mode 100644 index 4eb0f0e1c6..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {int} rat(s)/mouse/mice -text: 2 mice -expected: |- - [2] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml deleted file mode 100644 index 964fa6489e..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml +++ /dev/null @@ -1,5 +0,0 @@ -expression: |- - {int} rat(s)/mouse/mice -text: 1 mouse -expected: |- - [1] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml deleted file mode 100644 index c963dcf1c7..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} and {string} mice -text: three '' and 'handsome' mice -expected: |- - ["","handsome"] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml deleted file mode 100644 index cff2a2d1ec..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three '' mice -expected: |- - [""] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml deleted file mode 100644 index eb9ed537cd..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three '"blind"' mice -expected: |- - ["\"blind\""] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml deleted file mode 100644 index 4c2b0055b9..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three 'bl\'nd' mice -expected: |- - ["bl'nd"] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml deleted file mode 100644 index 6c8f4652a5..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {string} mice -text: three 'blind' mice -expected: |- - ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-word.yaml b/cucumber-expressions/go/testdata/expression/matches-word.yaml deleted file mode 100644 index 358fd3afd1..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-word.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three {word} mice -text: three blind mice -expected: |- - ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml b/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml deleted file mode 100644 index 384e3a48c3..0000000000 --- a/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: |- - {unknown} -text: something -exception: |- - This Cucumber Expression has a problem at column 1: - - {unknown} - ^-------^ - Undefined parameter type 'unknown'. - Please register a ParameterType for 'unknown' diff --git a/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml deleted file mode 100644 index 48b107f64e..0000000000 --- a/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml +++ /dev/null @@ -1,13 +0,0 @@ -expression: three blind/cripple mice -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, - {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, - {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, - {"type": "ALTERNATION", "start": 11, "end": 12, "text": "/"}, - {"type": "TEXT", "start": 12, "end": 19, "text": "cripple"}, - {"type": "WHITE_SPACE", "start": 19, "end": 20, "text": " "}, - {"type": "TEXT", "start": 20, "end": 24, "text": "mice"}, - {"type": "END_OF_LINE", "start": 24, "end": 24, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/alternation.yaml b/cucumber-expressions/go/testdata/tokens/alternation.yaml deleted file mode 100644 index a4920f22e5..0000000000 --- a/cucumber-expressions/go/testdata/tokens/alternation.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: blind/cripple -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 5, "text": "blind"}, - {"type": "ALTERNATION", "start": 5, "end": 6, "text": "/"}, - {"type": "TEXT", "start": 6, "end": 13, "text": "cripple"}, - {"type": "END_OF_LINE", "start": 13, "end": 13, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/empty-string.yaml b/cucumber-expressions/go/testdata/tokens/empty-string.yaml deleted file mode 100644 index 501f7522f2..0000000000 --- a/cucumber-expressions/go/testdata/tokens/empty-string.yaml +++ /dev/null @@ -1,6 +0,0 @@ -expression: "" -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "END_OF_LINE", "start": 0, "end": 0, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml deleted file mode 100644 index 5e206be084..0000000000 --- a/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml +++ /dev/null @@ -1,8 +0,0 @@ -expression: \[ -exception: |- - This Cucumber Expression has a problem at column 2: - - \[ - ^ - Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. - If you did mean to use an '\' you can use '\\' to escape it diff --git a/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml deleted file mode 100644 index 7e21f7ad19..0000000000 --- a/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: blind\ and\ famished\/cripple mice -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 29, "text": "blind and famished/cripple"}, - {"type": "WHITE_SPACE", "start": 29, "end": 30, "text": " "}, - {"type": "TEXT", "start": 30, "end": 34, "text": "mice"}, - {"type": "END_OF_LINE", "start": 34, "end": 34, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml b/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml deleted file mode 100644 index 6375ad52a5..0000000000 --- a/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: ' \/ ' -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "WHITE_SPACE", "start": 0, "end": 1, "text": " "}, - {"type": "TEXT", "start": 1, "end": 3, "text": "/"}, - {"type": "WHITE_SPACE", "start": 3, "end": 4, "text": " "}, - {"type": "END_OF_LINE", "start": 4, "end": 4, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml deleted file mode 100644 index a1bd00fd98..0000000000 --- a/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml +++ /dev/null @@ -1,8 +0,0 @@ -expression: \ -exception: |- - This Cucumber Expression has a problem at column 1: - - \ - ^ - The end of line can not be escaped. - You can use '\\' to escape the the '\' diff --git a/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml deleted file mode 100644 index 2b365b581c..0000000000 --- a/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml +++ /dev/null @@ -1,7 +0,0 @@ -expression: \(blind\) -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 9, "text": "(blind)"}, - {"type": "END_OF_LINE", "start": 9, "end": 9, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml deleted file mode 100644 index 2cdac4f35a..0000000000 --- a/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml +++ /dev/null @@ -1,7 +0,0 @@ -expression: \{string\} -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 10, "text": "{string}"}, - {"type": "END_OF_LINE", "start": 10, "end": 10, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-space.yaml b/cucumber-expressions/go/testdata/tokens/escaped-space.yaml deleted file mode 100644 index 912827a941..0000000000 --- a/cucumber-expressions/go/testdata/tokens/escaped-space.yaml +++ /dev/null @@ -1,7 +0,0 @@ -expression: '\ ' -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 2, "text": " "}, - {"type": "END_OF_LINE", "start": 2, "end": 2, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml deleted file mode 100644 index 2ddc6bb502..0000000000 --- a/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml +++ /dev/null @@ -1,13 +0,0 @@ -expression: three (blind) mice -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, - {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, - {"type": "BEGIN_OPTIONAL", "start": 6, "end": 7, "text": "("}, - {"type": "TEXT", "start": 7, "end": 12, "text": "blind"}, - {"type": "END_OPTIONAL", "start": 12, "end": 13, "text": ")"}, - {"type": "WHITE_SPACE", "start": 13, "end": 14, "text": " "}, - {"type": "TEXT", "start": 14, "end": 18, "text": "mice"}, - {"type": "END_OF_LINE", "start": 18, "end": 18, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/optional.yaml b/cucumber-expressions/go/testdata/tokens/optional.yaml deleted file mode 100644 index 35b1474a7c..0000000000 --- a/cucumber-expressions/go/testdata/tokens/optional.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: (blind) -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "BEGIN_OPTIONAL", "start": 0, "end": 1, "text": "("}, - {"type": "TEXT", "start": 1, "end": 6, "text": "blind"}, - {"type": "END_OPTIONAL", "start": 6, "end": 7, "text": ")"}, - {"type": "END_OF_LINE", "start": 7, "end": 7, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml deleted file mode 100644 index 5e98055ee6..0000000000 --- a/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml +++ /dev/null @@ -1,13 +0,0 @@ -expression: three {string} mice -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, - {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, - {"type": "BEGIN_PARAMETER", "start": 6, "end": 7, "text": "{"}, - {"type": "TEXT", "start": 7, "end": 13, "text": "string"}, - {"type": "END_PARAMETER", "start": 13, "end": 14, "text": "}"}, - {"type": "WHITE_SPACE", "start": 14, "end": 15, "text": " "}, - {"type": "TEXT", "start": 15, "end": 19, "text": "mice"}, - {"type": "END_OF_LINE", "start": 19, "end": 19, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/parameter.yaml b/cucumber-expressions/go/testdata/tokens/parameter.yaml deleted file mode 100644 index 460363c393..0000000000 --- a/cucumber-expressions/go/testdata/tokens/parameter.yaml +++ /dev/null @@ -1,9 +0,0 @@ -expression: "{string}" -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "BEGIN_PARAMETER", "start": 0, "end": 1, "text": "{"}, - {"type": "TEXT", "start": 1, "end": 7, "text": "string"}, - {"type": "END_PARAMETER", "start": 7, "end": 8, "text": "}"}, - {"type": "END_OF_LINE", "start": 8, "end": 8, "text": ""} - ] diff --git a/cucumber-expressions/go/testdata/tokens/phrase.yaml b/cucumber-expressions/go/testdata/tokens/phrase.yaml deleted file mode 100644 index e2cfccf7b4..0000000000 --- a/cucumber-expressions/go/testdata/tokens/phrase.yaml +++ /dev/null @@ -1,11 +0,0 @@ -expression: three blind mice -expected: |- - [ - {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, - {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, - {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, - {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, - {"type": "WHITE_SPACE", "start": 11, "end": 12, "text": " "}, - {"type": "TEXT", "start": 12, "end": 16, "text": "mice"}, - {"type": "END_OF_LINE", "start": 16, "end": 16, "text": ""} - ] From 0f0ab77b216c7c295914955fef381cbc52786935 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 19 Sep 2020 12:29:25 +0200 Subject: [PATCH 139/183] go test data again! --- .../ast/alternation-followed-by-optional.yaml | 17 ++++++++++++ .../go/testdata/ast/alternation-phrase.yaml | 16 +++++++++++ .../ast/alternation-with-parameter.yaml | 27 +++++++++++++++++++ .../alternation-with-unused-end-optional.yaml | 15 +++++++++++ ...lternation-with-unused-start-optional.yaml | 8 ++++++ .../ast/alternation-with-white-space.yaml | 12 +++++++++ .../go/testdata/ast/alternation.yaml | 12 +++++++++ .../go/testdata/ast/anonymous-parameter.yaml | 5 ++++ .../go/testdata/ast/closing-brace.yaml | 5 ++++ .../go/testdata/ast/closing-parenthesis.yaml | 5 ++++ .../go/testdata/ast/empty-alternation.yaml | 8 ++++++ .../go/testdata/ast/empty-alternations.yaml | 9 +++++++ .../go/testdata/ast/empty-string.yaml | 3 +++ .../go/testdata/ast/escaped-alternation.yaml | 5 ++++ .../go/testdata/ast/escaped-backslash.yaml | 5 ++++ .../ast/escaped-opening-parenthesis.yaml | 5 ++++ ...escaped-optional-followed-by-optional.yaml | 15 +++++++++++ .../testdata/ast/escaped-optional-phrase.yaml | 10 +++++++ .../go/testdata/ast/escaped-optional.yaml | 7 +++++ .../go/testdata/ast/opening-brace.yaml | 8 ++++++ .../go/testdata/ast/opening-parenthesis.yaml | 8 ++++++ .../optional-containing-escaped-optional.yaml | 14 ++++++++++ .../go/testdata/ast/optional-phrase.yaml | 12 +++++++++ .../go/testdata/ast/optional.yaml | 7 +++++ .../go/testdata/ast/parameter.yaml | 7 +++++ .../go/testdata/ast/phrase.yaml | 9 +++++++ .../go/testdata/ast/unfinished-parameter.yaml | 8 ++++++ ...lows-escaped-optional-parameter-types.yaml | 4 +++ ...llows-parameter-type-in-alternation-1.yaml | 4 +++ ...llows-parameter-type-in-alternation-2.yaml | 4 +++ ...low-parameter-adjacent-to-alternation.yaml | 5 ++++ ...lternative-by-adjacent-left-parameter.yaml | 10 +++++++ ...mpty-alternative-by-adjacent-optional.yaml | 9 +++++++ ...ternative-by-adjacent-right-parameter.yaml | 9 +++++++ ...ow-alternation-with-empty-alternative.yaml | 9 +++++++ .../does-not-allow-empty-optional.yaml | 9 +++++++ ...es-not-allow-optional-parameter-types.yaml | 9 +++++++ ...llow-parameter-type-with-left-bracket.yaml | 10 +++++++ .../does-not-match-misquoted-string.yaml | 4 +++ .../expression/doesnt-match-float-as-int.yaml | 5 ++++ ...tches-alternation-in-optional-as-text.yaml | 4 +++ .../expression/matches-alternation.yaml | 4 +++ .../matches-anonymous-parameter-type.yaml | 5 ++++ ...empty-string-along-with-other-strings.yaml | 4 +++ ...e-quoted-empty-string-as-empty-string.yaml | 4 +++ ...oted-string-with-escaped-double-quote.yaml | 4 +++ ...uble-quoted-string-with-single-quotes.yaml | 4 +++ .../matches-double-quoted-string.yaml | 4 +++ .../matches-doubly-escaped-parenthesis.yaml | 4 +++ .../matches-doubly-escaped-slash-1.yaml | 4 +++ .../matches-doubly-escaped-slash-2.yaml | 4 +++ .../matches-escaped-parenthesis-1.yaml | 4 +++ .../matches-escaped-parenthesis-2.yaml | 4 +++ .../matches-escaped-parenthesis-3.yaml | 4 +++ .../expression/matches-escaped-slash.yaml | 4 +++ .../testdata/expression/matches-float-1.yaml | 5 ++++ .../testdata/expression/matches-float-2.yaml | 5 ++++ .../go/testdata/expression/matches-int.yaml | 5 ++++ ...atches-multiple-double-quoted-strings.yaml | 4 +++ ...atches-multiple-single-quoted-strings.yaml | 4 +++ ...matches-optional-before-alternation-1.yaml | 4 +++ ...matches-optional-before-alternation-2.yaml | 4 +++ ...e-alternation-with-regex-characters-1.yaml | 4 +++ ...e-alternation-with-regex-characters-2.yaml | 4 +++ .../matches-optional-in-alternation-1.yaml | 5 ++++ .../matches-optional-in-alternation-2.yaml | 5 ++++ .../matches-optional-in-alternation-3.yaml | 5 ++++ ...empty-string-along-with-other-strings.yaml | 4 +++ ...e-quoted-empty-string-as-empty-string.yaml | 4 +++ ...ngle-quoted-string-with-double-quotes.yaml | 4 +++ ...oted-string-with-escaped-single-quote.yaml | 4 +++ .../matches-single-quoted-string.yaml | 4 +++ .../go/testdata/expression/matches-word.yaml | 4 +++ .../throws-unknown-parameter-type.yaml | 10 +++++++ .../testdata/tokens/alternation-phrase.yaml | 13 +++++++++ .../go/testdata/tokens/alternation.yaml | 9 +++++++ .../go/testdata/tokens/empty-string.yaml | 6 +++++ .../tokens/escape-non-reserved-character.yaml | 8 ++++++ .../testdata/tokens/escaped-alternation.yaml | 9 +++++++ ...ed-char-has-start-index-of-text-token.yaml | 9 +++++++ .../testdata/tokens/escaped-end-of-line.yaml | 8 ++++++ .../go/testdata/tokens/escaped-optional.yaml | 7 +++++ .../go/testdata/tokens/escaped-parameter.yaml | 7 +++++ .../go/testdata/tokens/escaped-space.yaml | 7 +++++ .../go/testdata/tokens/optional-phrase.yaml | 13 +++++++++ .../go/testdata/tokens/optional.yaml | 9 +++++++ .../go/testdata/tokens/parameter-phrase.yaml | 13 +++++++++ .../go/testdata/tokens/parameter.yaml | 9 +++++++ .../go/testdata/tokens/phrase.yaml | 11 ++++++++ 89 files changed, 640 insertions(+) create mode 100644 cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml create mode 100644 cucumber-expressions/go/testdata/ast/alternation.yaml create mode 100644 cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/ast/closing-brace.yaml create mode 100644 cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml create mode 100644 cucumber-expressions/go/testdata/ast/empty-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/ast/empty-alternations.yaml create mode 100644 cucumber-expressions/go/testdata/ast/empty-string.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-backslash.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/ast/escaped-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/opening-brace.yaml create mode 100644 cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml create mode 100644 cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/optional-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/ast/optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/parameter.yaml create mode 100644 cucumber-expressions/go/testdata/ast/phrase.yaml create mode 100644 cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml create mode 100644 cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-float-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-float-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-int.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml create mode 100644 cucumber-expressions/go/testdata/expression/matches-word.yaml create mode 100644 cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/alternation.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/empty-string.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-optional.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/escaped-space.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/optional-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/optional.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/parameter.yaml create mode 100644 cucumber-expressions/go/testdata/tokens/phrase.yaml diff --git a/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml new file mode 100644 index 0000000000..0bfb53a534 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-followed-by-optional.yaml @@ -0,0 +1,17 @@ +expression: three blind\ rat/cat(s) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 16, "token": "blind rat"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 17, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 17, "end": 20, "token": "cat"}, + {"type": "OPTIONAL_NODE", "start": 20, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": "s"} + ]} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml new file mode 100644 index 0000000000..9a243822d5 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-phrase.yaml @@ -0,0 +1,16 @@ +expression: three hungry/blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 12, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 12, "token": "hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 13, "end": 18, "token": "blind"} + ]} + ]}, + {"type": "TEXT_NODE", "start": 18, "end": 19, "token": " "}, + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml new file mode 100644 index 0000000000..c5daf32bd7 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-with-parameter.yaml @@ -0,0 +1,27 @@ +expression: I select the {int}st/nd/rd/th +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "I"}, + {"type": "TEXT_NODE", "start": 1, "end": 2, "token": " "}, + {"type": "TEXT_NODE", "start": 2, "end": 8, "token": "select"}, + {"type": "TEXT_NODE", "start": 8, "end": 9, "token": " "}, + {"type": "TEXT_NODE", "start": 9, "end": 12, "token": "the"}, + {"type": "TEXT_NODE", "start": 12, "end": 13, "token": " "}, + {"type": "PARAMETER_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 14, "end": 17, "token": "int"} + ]}, + {"type": "ALTERNATION_NODE", "start": 18, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 18, "end": 20, "nodes": [ + {"type": "TEXT_NODE", "start": 18, "end": 20, "token": "st"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 21, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 23, "token": "nd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 24, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 24, "end": 26, "token": "rd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 27, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 27, "end": 29, "token": "th"} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml new file mode 100644 index 0000000000..842838b75f --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-with-unused-end-optional.yaml @@ -0,0 +1,15 @@ +expression: three )blind\ mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 7, "token": ")"}, + {"type": "TEXT_NODE", "start": 7, "end": 18, "token": "blind mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 19, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml new file mode 100644 index 0000000000..e2f0584556 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-with-unused-start-optional.yaml @@ -0,0 +1,8 @@ +expression: three blind\ mice/rats( +exception: |- + This Cucumber Expression has a problem at column 23: + + three blind\ mice/rats( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml new file mode 100644 index 0000000000..eedd57dd21 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation-with-white-space.yaml @@ -0,0 +1,12 @@ +expression: '\ three\ hungry/blind\ mice\ ' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 15, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 15, "token": " three hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 16, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 16, "end": 29, "token": "blind mice "} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/alternation.yaml b/cucumber-expressions/go/testdata/ast/alternation.yaml new file mode 100644 index 0000000000..88df8325fe --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/alternation.yaml @@ -0,0 +1,12 @@ +expression: mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 4, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 4, "token": "mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 5, "end": 9, "nodes": [ + {"type": "TEXT_NODE", "start": 5, "end": 9, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml new file mode 100644 index 0000000000..2c4d339333 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/anonymous-parameter.yaml @@ -0,0 +1,5 @@ +expression: "{}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 2, "nodes": []} + ]} diff --git a/cucumber-expressions/go/testdata/ast/closing-brace.yaml b/cucumber-expressions/go/testdata/ast/closing-brace.yaml new file mode 100644 index 0000000000..1bafd9c6a8 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/closing-brace.yaml @@ -0,0 +1,5 @@ +expression: "}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "}"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml new file mode 100644 index 0000000000..23daf7bcd3 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/closing-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: ) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": ")"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-alternation.yaml b/cucumber-expressions/go/testdata/ast/empty-alternation.yaml new file mode 100644 index 0000000000..6d810fc8f3 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/empty-alternation.yaml @@ -0,0 +1,8 @@ +expression: / +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-alternations.yaml b/cucumber-expressions/go/testdata/ast/empty-alternations.yaml new file mode 100644 index 0000000000..f8d4dd4cf8 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/empty-alternations.yaml @@ -0,0 +1,9 @@ +expression: '//' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/empty-string.yaml b/cucumber-expressions/go/testdata/ast/empty-string.yaml new file mode 100644 index 0000000000..4d33c2dc76 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/empty-string.yaml @@ -0,0 +1,3 @@ +expression: "" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 0, "nodes": []} diff --git a/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml new file mode 100644 index 0000000000..3ed9c37674 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-alternation.yaml @@ -0,0 +1,5 @@ +expression: 'mice\/rats' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 10, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 10, "token": "mice/rats"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml new file mode 100644 index 0000000000..da2d008e1e --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-backslash.yaml @@ -0,0 +1,5 @@ +expression: '\\' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\\"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml new file mode 100644 index 0000000000..afafc59eb8 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-opening-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: '\(' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "("} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml new file mode 100644 index 0000000000..1e4746291b --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -0,0 +1,15 @@ +expression: three \((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 8, "token": "("}, + {"type": "OPTIONAL_NODE", "start": 8, "end": 14, "nodes": [ + {"type": "TEXT_NODE", "start": 9, "end": 13, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": ")"}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml new file mode 100644 index 0000000000..832249e2a7 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-optional-phrase.yaml @@ -0,0 +1,10 @@ +expression: three \(blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 19, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 13, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": ")"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 19, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/escaped-optional.yaml b/cucumber-expressions/go/testdata/ast/escaped-optional.yaml new file mode 100644 index 0000000000..4c2b457d6f --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: '\(blind)' + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 7, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": ")"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/opening-brace.yaml b/cucumber-expressions/go/testdata/ast/opening-brace.yaml new file mode 100644 index 0000000000..916a674a36 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/opening-brace.yaml @@ -0,0 +1,8 @@ +expression: '{' +exception: |- + This Cucumber Expression has a problem at column 1: + + { + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml new file mode 100644 index 0000000000..929d6ae304 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/opening-parenthesis.yaml @@ -0,0 +1,8 @@ +expression: ( +exception: |- + This Cucumber Expression has a problem at column 1: + + ( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml new file mode 100644 index 0000000000..f09199a454 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml @@ -0,0 +1,14 @@ +expression: three ((very\) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, + {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/optional-phrase.yaml b/cucumber-expressions/go/testdata/ast/optional-phrase.yaml new file mode 100644 index 0000000000..0ef4fb3f95 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/optional-phrase.yaml @@ -0,0 +1,12 @@ +expression: three (blind) mice + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 12, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 18, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/optional.yaml b/cucumber-expressions/go/testdata/ast/optional.yaml new file mode 100644 index 0000000000..6ce2b632e7 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/optional.yaml @@ -0,0 +1,7 @@ +expression: (blind) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 6, "token": "blind"} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/parameter.yaml b/cucumber-expressions/go/testdata/ast/parameter.yaml new file mode 100644 index 0000000000..92ac8c147e --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/parameter.yaml @@ -0,0 +1,7 @@ +expression: "{string}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 7, "token": "string"} + ]} + ]} diff --git a/cucumber-expressions/go/testdata/ast/phrase.yaml b/cucumber-expressions/go/testdata/ast/phrase.yaml new file mode 100644 index 0000000000..ba340d0122 --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/phrase.yaml @@ -0,0 +1,9 @@ +expression: three blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 11, "token": "blind"}, + {"type": "TEXT_NODE", "start": 11, "end": 12, "token": " "}, + {"type": "TEXT_NODE", "start": 12, "end": 16, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml new file mode 100644 index 0000000000..d02f9b4ccf --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/unfinished-parameter.yaml @@ -0,0 +1,8 @@ +expression: "{string" +exception: |- + This Cucumber Expression has a problem at column 1: + + {string + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml b/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml new file mode 100644 index 0000000000..a00b45acef --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/allows-escaped-optional-parameter-types.yaml @@ -0,0 +1,4 @@ +expression: \({int}) +text: (3) +expected: |- + [3] diff --git a/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml new file mode 100644 index 0000000000..bb1a6f21b1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: i18n +expected: |- + [18] diff --git a/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml new file mode 100644 index 0000000000..cdddce7d84 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/allows-parameter-type-in-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: a11y +expected: |- + [11] diff --git a/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml b/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml new file mode 100644 index 0000000000..9e2ecdfbe1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml @@ -0,0 +1,5 @@ +expression: |- + {int}st/nd/rd/th +text: 3rd +expected: |- + [3] diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml new file mode 100644 index 0000000000..b32540a4a9 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml @@ -0,0 +1,10 @@ +expression: |- + {int}/x +text: '3' +exception: |- + This Cucumber Expression has a problem at column 6: + + {int}/x + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml new file mode 100644 index 0000000000..a0aab0e5a9 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml @@ -0,0 +1,9 @@ +expression: three (brown)/black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (brown)/black mice + ^-----^ + An alternative may not exclusively contain optionals. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml new file mode 100644 index 0000000000..50250f00aa --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml @@ -0,0 +1,9 @@ +expression: x/{int} +text: '3' +exception: |- + This Cucumber Expression has a problem at column 3: + + x/{int} + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml new file mode 100644 index 0000000000..b724cfa77f --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml @@ -0,0 +1,9 @@ +expression: three brown//black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three brown//black mice + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml new file mode 100644 index 0000000000..00e341af0b --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-empty-optional.yaml @@ -0,0 +1,9 @@ +expression: three () mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three () mice + ^^ + An optional must contain some text. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml new file mode 100644 index 0000000000..b88061e9b4 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-optional-parameter-types.yaml @@ -0,0 +1,9 @@ +expression: ({int}) +text: '3' +exception: |- + This Cucumber Expression has a problem at column 2: + + ({int}) + ^---^ + An optional may not contain a parameter type. + If you did not mean to use an parameter type you can use '\{' to escape the the '{' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml new file mode 100644 index 0000000000..1dd65aa276 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml @@ -0,0 +1,10 @@ +expression: |- + {[string]} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {[string]} + ^--------^ + Parameter names may not contain '[]()$.|?*+'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml b/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml new file mode 100644 index 0000000000..18023180af --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-match-misquoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind' mice +expected: |- + null diff --git a/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml b/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml new file mode 100644 index 0000000000..d66b586430 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/doesnt-match-float-as-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '1.22' +expected: |- + null diff --git a/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml new file mode 100644 index 0000000000..15fe78bf53 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml @@ -0,0 +1,4 @@ +expression: three( brown/black) mice +text: three brown/black mice +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-alternation.yaml b/cucumber-expressions/go/testdata/expression/matches-alternation.yaml new file mode 100644 index 0000000000..20a9b9a728 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-alternation.yaml @@ -0,0 +1,4 @@ +expression: mice/rats and rats\/mice +text: rats and rats/mice +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml b/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml new file mode 100644 index 0000000000..fc954960df --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-anonymous-parameter-type.yaml @@ -0,0 +1,5 @@ +expression: |- + {} +text: '0.22' +expected: |- + ["0.22"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c3e1962e22 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "" and "handsome" mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..89315b62ef --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "" mice +expected: |- + [""] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml new file mode 100644 index 0000000000..fe30a044c1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "bl\"nd" mice +expected: |- + ["bl\"nd"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml new file mode 100644 index 0000000000..25fcf304c1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "'blind'" mice +expected: |- + ["'blind'"] diff --git a/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml new file mode 100644 index 0000000000..8b0560f332 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-double-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml new file mode 100644 index 0000000000..902a084103 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-parenthesis.yaml @@ -0,0 +1,4 @@ +expression: three \\(exceptionally) \\{string} mice +text: three \exceptionally \"blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml new file mode 100644 index 0000000000..94e531eca7 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-1.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: 12\ +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml new file mode 100644 index 0000000000..9c9c735b86 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-doubly-escaped-slash-2.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: '2020' +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml new file mode 100644 index 0000000000..171df89ee1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-1.yaml @@ -0,0 +1,4 @@ +expression: three \(exceptionally) \{string} mice +text: three (exceptionally) {string} mice +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-2.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-escaped-parenthesis-3.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml b/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml new file mode 100644 index 0000000000..d8b3933399 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-escaped-slash.yaml @@ -0,0 +1,4 @@ +expression: 12\/2020 +text: 12/2020 +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-float-1.yaml b/cucumber-expressions/go/testdata/expression/matches-float-1.yaml new file mode 100644 index 0000000000..fe7e8b1869 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-float-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '0.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/go/testdata/expression/matches-float-2.yaml b/cucumber-expressions/go/testdata/expression/matches-float-2.yaml new file mode 100644 index 0000000000..c1e5894eac --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-float-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/go/testdata/expression/matches-int.yaml b/cucumber-expressions/go/testdata/expression/matches-int.yaml new file mode 100644 index 0000000000..bcd3763886 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '22' +expected: |- + [22] diff --git a/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml new file mode 100644 index 0000000000..6c74bc2350 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-multiple-double-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "blind" and "crippled" mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml new file mode 100644 index 0000000000..5037821c14 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-multiple-single-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three 'blind' and 'crippled' mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml new file mode 100644 index 0000000000..821776715b --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three brown mice +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml new file mode 100644 index 0000000000..71b3a341f1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three rats +expected: |- + [] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml new file mode 100644 index 0000000000..2632f410ce --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 2 seconds? +expected: |- + [2] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml new file mode 100644 index 0000000000..7b30f667bc --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 1 second. +expected: |- + [1] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml new file mode 100644 index 0000000000..6574bb4bdf --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 3 rats +expected: |- + [3] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml new file mode 100644 index 0000000000..4eb0f0e1c6 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 2 mice +expected: |- + [2] diff --git a/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml new file mode 100644 index 0000000000..964fa6489e --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-optional-in-alternation-3.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 1 mouse +expected: |- + [1] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c963dcf1c7 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three '' and 'handsome' mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..cff2a2d1ec --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '' mice +expected: |- + [""] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml new file mode 100644 index 0000000000..eb9ed537cd --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '"blind"' mice +expected: |- + ["\"blind\""] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml new file mode 100644 index 0000000000..4c2b0055b9 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'bl\'nd' mice +expected: |- + ["bl'nd"] diff --git a/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml new file mode 100644 index 0000000000..6c8f4652a5 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-single-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'blind' mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/matches-word.yaml b/cucumber-expressions/go/testdata/expression/matches-word.yaml new file mode 100644 index 0000000000..358fd3afd1 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/matches-word.yaml @@ -0,0 +1,4 @@ +expression: three {word} mice +text: three blind mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml b/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml new file mode 100644 index 0000000000..384e3a48c3 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/throws-unknown-parameter-type.yaml @@ -0,0 +1,10 @@ +expression: |- + {unknown} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {unknown} + ^-------^ + Undefined parameter type 'unknown'. + Please register a ParameterType for 'unknown' diff --git a/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml new file mode 100644 index 0000000000..48b107f64e --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/alternation-phrase.yaml @@ -0,0 +1,13 @@ +expression: three blind/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "ALTERNATION", "start": 11, "end": 12, "text": "/"}, + {"type": "TEXT", "start": 12, "end": 19, "text": "cripple"}, + {"type": "WHITE_SPACE", "start": 19, "end": 20, "text": " "}, + {"type": "TEXT", "start": 20, "end": 24, "text": "mice"}, + {"type": "END_OF_LINE", "start": 24, "end": 24, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/alternation.yaml b/cucumber-expressions/go/testdata/tokens/alternation.yaml new file mode 100644 index 0000000000..a4920f22e5 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/alternation.yaml @@ -0,0 +1,9 @@ +expression: blind/cripple +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "blind"}, + {"type": "ALTERNATION", "start": 5, "end": 6, "text": "/"}, + {"type": "TEXT", "start": 6, "end": 13, "text": "cripple"}, + {"type": "END_OF_LINE", "start": 13, "end": 13, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/empty-string.yaml b/cucumber-expressions/go/testdata/tokens/empty-string.yaml new file mode 100644 index 0000000000..501f7522f2 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/empty-string.yaml @@ -0,0 +1,6 @@ +expression: "" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "END_OF_LINE", "start": 0, "end": 0, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml new file mode 100644 index 0000000000..5e206be084 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escape-non-reserved-character.yaml @@ -0,0 +1,8 @@ +expression: \[ +exception: |- + This Cucumber Expression has a problem at column 2: + + \[ + ^ + Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. + If you did mean to use an '\' you can use '\\' to escape it diff --git a/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml new file mode 100644 index 0000000000..7e21f7ad19 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-alternation.yaml @@ -0,0 +1,9 @@ +expression: blind\ and\ famished\/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 29, "text": "blind and famished/cripple"}, + {"type": "WHITE_SPACE", "start": 29, "end": 30, "text": " "}, + {"type": "TEXT", "start": 30, "end": 34, "text": "mice"}, + {"type": "END_OF_LINE", "start": 34, "end": 34, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml b/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml new file mode 100644 index 0000000000..6375ad52a5 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml @@ -0,0 +1,9 @@ +expression: ' \/ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "WHITE_SPACE", "start": 0, "end": 1, "text": " "}, + {"type": "TEXT", "start": 1, "end": 3, "text": "/"}, + {"type": "WHITE_SPACE", "start": 3, "end": 4, "text": " "}, + {"type": "END_OF_LINE", "start": 4, "end": 4, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml new file mode 100644 index 0000000000..a1bd00fd98 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-end-of-line.yaml @@ -0,0 +1,8 @@ +expression: \ +exception: |- + This Cucumber Expression has a problem at column 1: + + \ + ^ + The end of line can not be escaped. + You can use '\\' to escape the the '\' diff --git a/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml new file mode 100644 index 0000000000..2b365b581c --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: \(blind\) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 9, "text": "(blind)"}, + {"type": "END_OF_LINE", "start": 9, "end": 9, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml new file mode 100644 index 0000000000..2cdac4f35a --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-parameter.yaml @@ -0,0 +1,7 @@ +expression: \{string\} +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 10, "text": "{string}"}, + {"type": "END_OF_LINE", "start": 10, "end": 10, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/escaped-space.yaml b/cucumber-expressions/go/testdata/tokens/escaped-space.yaml new file mode 100644 index 0000000000..912827a941 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/escaped-space.yaml @@ -0,0 +1,7 @@ +expression: '\ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 2, "text": " "}, + {"type": "END_OF_LINE", "start": 2, "end": 2, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml new file mode 100644 index 0000000000..2ddc6bb502 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/optional-phrase.yaml @@ -0,0 +1,13 @@ +expression: three (blind) mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_OPTIONAL", "start": 6, "end": 7, "text": "("}, + {"type": "TEXT", "start": 7, "end": 12, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 12, "end": 13, "text": ")"}, + {"type": "WHITE_SPACE", "start": 13, "end": 14, "text": " "}, + {"type": "TEXT", "start": 14, "end": 18, "text": "mice"}, + {"type": "END_OF_LINE", "start": 18, "end": 18, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/optional.yaml b/cucumber-expressions/go/testdata/tokens/optional.yaml new file mode 100644 index 0000000000..35b1474a7c --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/optional.yaml @@ -0,0 +1,9 @@ +expression: (blind) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_OPTIONAL", "start": 0, "end": 1, "text": "("}, + {"type": "TEXT", "start": 1, "end": 6, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 6, "end": 7, "text": ")"}, + {"type": "END_OF_LINE", "start": 7, "end": 7, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml new file mode 100644 index 0000000000..5e98055ee6 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/parameter-phrase.yaml @@ -0,0 +1,13 @@ +expression: three {string} mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_PARAMETER", "start": 6, "end": 7, "text": "{"}, + {"type": "TEXT", "start": 7, "end": 13, "text": "string"}, + {"type": "END_PARAMETER", "start": 13, "end": 14, "text": "}"}, + {"type": "WHITE_SPACE", "start": 14, "end": 15, "text": " "}, + {"type": "TEXT", "start": 15, "end": 19, "text": "mice"}, + {"type": "END_OF_LINE", "start": 19, "end": 19, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/parameter.yaml b/cucumber-expressions/go/testdata/tokens/parameter.yaml new file mode 100644 index 0000000000..460363c393 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/parameter.yaml @@ -0,0 +1,9 @@ +expression: "{string}" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_PARAMETER", "start": 0, "end": 1, "text": "{"}, + {"type": "TEXT", "start": 1, "end": 7, "text": "string"}, + {"type": "END_PARAMETER", "start": 7, "end": 8, "text": "}"}, + {"type": "END_OF_LINE", "start": 8, "end": 8, "text": ""} + ] diff --git a/cucumber-expressions/go/testdata/tokens/phrase.yaml b/cucumber-expressions/go/testdata/tokens/phrase.yaml new file mode 100644 index 0000000000..e2cfccf7b4 --- /dev/null +++ b/cucumber-expressions/go/testdata/tokens/phrase.yaml @@ -0,0 +1,11 @@ +expression: three blind mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "WHITE_SPACE", "start": 11, "end": 12, "text": " "}, + {"type": "TEXT", "start": 12, "end": 16, "text": "mice"}, + {"type": "END_OF_LINE", "start": 16, "end": 16, "text": ""} + ] From ba080f631404b067f4a8bb075780eb4bfbc879f9 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 20 Sep 2020 11:12:25 +0200 Subject: [PATCH 140/183] Implement failing tokenizer tests in js --- cucumber-expressions/javascript/package.json | 1 + cucumber-expressions/javascript/src/Ast.ts | 5 ++++ .../src/CucumberExpressionTokenizer.ts | 11 +++++++ .../test/CucumberExpressionTokenizerTest.ts | 30 +++++++++++++++++++ .../testdata/tokens/alternation-phrase.yaml | 13 ++++++++ .../testdata/tokens/alternation.yaml | 9 ++++++ .../testdata/tokens/empty-string.yaml | 6 ++++ .../tokens/escape-non-reserved-character.yaml | 8 +++++ .../testdata/tokens/escaped-alternation.yaml | 9 ++++++ ...ed-char-has-start-index-of-text-token.yaml | 9 ++++++ .../testdata/tokens/escaped-end-of-line.yaml | 8 +++++ .../testdata/tokens/escaped-optional.yaml | 7 +++++ .../testdata/tokens/escaped-parameter.yaml | 7 +++++ .../testdata/tokens/escaped-space.yaml | 7 +++++ .../testdata/tokens/optional-phrase.yaml | 13 ++++++++ .../javascript/testdata/tokens/optional.yaml | 9 ++++++ .../testdata/tokens/parameter-phrase.yaml | 13 ++++++++ .../javascript/testdata/tokens/parameter.yaml | 9 ++++++ .../javascript/testdata/tokens/phrase.yaml | 11 +++++++ 19 files changed, 185 insertions(+) create mode 100644 cucumber-expressions/javascript/src/Ast.ts create mode 100644 cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts create mode 100644 cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts create mode 100644 cucumber-expressions/javascript/testdata/tokens/alternation-phrase.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/alternation.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/empty-string.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/escape-non-reserved-character.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/escaped-alternation.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/escaped-end-of-line.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/escaped-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/escaped-parameter.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/escaped-space.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/optional-phrase.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/parameter-phrase.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/parameter.yaml create mode 100644 cucumber-expressions/javascript/testdata/tokens/phrase.yaml diff --git a/cucumber-expressions/javascript/package.json b/cucumber-expressions/javascript/package.json index 7ca0ba157f..4eb38aa9f5 100644 --- a/cucumber-expressions/javascript/package.json +++ b/cucumber-expressions/javascript/package.json @@ -31,6 +31,7 @@ "devDependencies": { "@types/mocha": "^8.0.1", "@types/node": "^14.0.27", + "@types/js-yaml": "^3.12.5", "@typescript-eslint/eslint-plugin": "^3.8.0", "@typescript-eslint/parser": "^3.8.0", "eslint": "^7.6.0", diff --git a/cucumber-expressions/javascript/src/Ast.ts b/cucumber-expressions/javascript/src/Ast.ts new file mode 100644 index 0000000000..a0a1083b9f --- /dev/null +++ b/cucumber-expressions/javascript/src/Ast.ts @@ -0,0 +1,5 @@ +export default class Token { + constructor() { + // TODO: + } +} diff --git a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts new file mode 100644 index 0000000000..014f5dbcfc --- /dev/null +++ b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts @@ -0,0 +1,11 @@ +import Token from './Ast' + +export default class CucumberExpressionTokenizer { + constructor() { + // TODO: + } + + tokenize(expression: string): ReadonlyArray { + return [] + } +} diff --git a/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts new file mode 100644 index 0000000000..9b7afff1a5 --- /dev/null +++ b/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts @@ -0,0 +1,30 @@ +import fs from 'fs' +// eslint-disable-next-line node/no-extraneous-import +import yaml from 'js-yaml' // why? +import CucumberExpressionTokenizer from '../src/CucumberExpressionTokenizer' +import assert from 'assert' + +interface Expectation { + expression: string + expected?: string + exception?: string +} + +describe('Cucumber expression tokenizer', () => { + fs.readdirSync('testdata/tokens').forEach((testcase) => { + it(`${testcase}`, () => { + const expectation = yaml.safeLoad( + fs.readFileSync(`testdata/tokens/${testcase}`, 'utf-8') + ) as Expectation + const tokenizer = new CucumberExpressionTokenizer() + if (expectation.exception == undefined) { + const tokens = tokenizer.tokenize(expectation.expression) + assert.deepStrictEqual(tokens, JSON.parse(expectation.expected)) + } else { + assert.throws(() => { + tokenizer.tokenize(expectation.expression) + }, expectation.exception) + } + }) + }) +}) diff --git a/cucumber-expressions/javascript/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/javascript/testdata/tokens/alternation-phrase.yaml new file mode 100644 index 0000000000..48b107f64e --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/alternation-phrase.yaml @@ -0,0 +1,13 @@ +expression: three blind/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "ALTERNATION", "start": 11, "end": 12, "text": "/"}, + {"type": "TEXT", "start": 12, "end": 19, "text": "cripple"}, + {"type": "WHITE_SPACE", "start": 19, "end": 20, "text": " "}, + {"type": "TEXT", "start": 20, "end": 24, "text": "mice"}, + {"type": "END_OF_LINE", "start": 24, "end": 24, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/alternation.yaml b/cucumber-expressions/javascript/testdata/tokens/alternation.yaml new file mode 100644 index 0000000000..a4920f22e5 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/alternation.yaml @@ -0,0 +1,9 @@ +expression: blind/cripple +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "blind"}, + {"type": "ALTERNATION", "start": 5, "end": 6, "text": "/"}, + {"type": "TEXT", "start": 6, "end": 13, "text": "cripple"}, + {"type": "END_OF_LINE", "start": 13, "end": 13, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/empty-string.yaml b/cucumber-expressions/javascript/testdata/tokens/empty-string.yaml new file mode 100644 index 0000000000..501f7522f2 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/empty-string.yaml @@ -0,0 +1,6 @@ +expression: "" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "END_OF_LINE", "start": 0, "end": 0, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/javascript/testdata/tokens/escape-non-reserved-character.yaml new file mode 100644 index 0000000000..5e206be084 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/escape-non-reserved-character.yaml @@ -0,0 +1,8 @@ +expression: \[ +exception: |- + This Cucumber Expression has a problem at column 2: + + \[ + ^ + Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. + If you did mean to use an '\' you can use '\\' to escape it diff --git a/cucumber-expressions/javascript/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/javascript/testdata/tokens/escaped-alternation.yaml new file mode 100644 index 0000000000..7e21f7ad19 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/escaped-alternation.yaml @@ -0,0 +1,9 @@ +expression: blind\ and\ famished\/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 29, "text": "blind and famished/cripple"}, + {"type": "WHITE_SPACE", "start": 29, "end": 30, "text": " "}, + {"type": "TEXT", "start": 30, "end": 34, "text": "mice"}, + {"type": "END_OF_LINE", "start": 34, "end": 34, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml b/cucumber-expressions/javascript/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml new file mode 100644 index 0000000000..6375ad52a5 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml @@ -0,0 +1,9 @@ +expression: ' \/ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "WHITE_SPACE", "start": 0, "end": 1, "text": " "}, + {"type": "TEXT", "start": 1, "end": 3, "text": "/"}, + {"type": "WHITE_SPACE", "start": 3, "end": 4, "text": " "}, + {"type": "END_OF_LINE", "start": 4, "end": 4, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/javascript/testdata/tokens/escaped-end-of-line.yaml new file mode 100644 index 0000000000..a1bd00fd98 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/escaped-end-of-line.yaml @@ -0,0 +1,8 @@ +expression: \ +exception: |- + This Cucumber Expression has a problem at column 1: + + \ + ^ + The end of line can not be escaped. + You can use '\\' to escape the the '\' diff --git a/cucumber-expressions/javascript/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/javascript/testdata/tokens/escaped-optional.yaml new file mode 100644 index 0000000000..2b365b581c --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: \(blind\) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 9, "text": "(blind)"}, + {"type": "END_OF_LINE", "start": 9, "end": 9, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/javascript/testdata/tokens/escaped-parameter.yaml new file mode 100644 index 0000000000..2cdac4f35a --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/escaped-parameter.yaml @@ -0,0 +1,7 @@ +expression: \{string\} +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 10, "text": "{string}"}, + {"type": "END_OF_LINE", "start": 10, "end": 10, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/escaped-space.yaml b/cucumber-expressions/javascript/testdata/tokens/escaped-space.yaml new file mode 100644 index 0000000000..912827a941 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/escaped-space.yaml @@ -0,0 +1,7 @@ +expression: '\ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 2, "text": " "}, + {"type": "END_OF_LINE", "start": 2, "end": 2, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/javascript/testdata/tokens/optional-phrase.yaml new file mode 100644 index 0000000000..2ddc6bb502 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/optional-phrase.yaml @@ -0,0 +1,13 @@ +expression: three (blind) mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_OPTIONAL", "start": 6, "end": 7, "text": "("}, + {"type": "TEXT", "start": 7, "end": 12, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 12, "end": 13, "text": ")"}, + {"type": "WHITE_SPACE", "start": 13, "end": 14, "text": " "}, + {"type": "TEXT", "start": 14, "end": 18, "text": "mice"}, + {"type": "END_OF_LINE", "start": 18, "end": 18, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/optional.yaml b/cucumber-expressions/javascript/testdata/tokens/optional.yaml new file mode 100644 index 0000000000..35b1474a7c --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/optional.yaml @@ -0,0 +1,9 @@ +expression: (blind) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_OPTIONAL", "start": 0, "end": 1, "text": "("}, + {"type": "TEXT", "start": 1, "end": 6, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 6, "end": 7, "text": ")"}, + {"type": "END_OF_LINE", "start": 7, "end": 7, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/javascript/testdata/tokens/parameter-phrase.yaml new file mode 100644 index 0000000000..5e98055ee6 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/parameter-phrase.yaml @@ -0,0 +1,13 @@ +expression: three {string} mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_PARAMETER", "start": 6, "end": 7, "text": "{"}, + {"type": "TEXT", "start": 7, "end": 13, "text": "string"}, + {"type": "END_PARAMETER", "start": 13, "end": 14, "text": "}"}, + {"type": "WHITE_SPACE", "start": 14, "end": 15, "text": " "}, + {"type": "TEXT", "start": 15, "end": 19, "text": "mice"}, + {"type": "END_OF_LINE", "start": 19, "end": 19, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/parameter.yaml b/cucumber-expressions/javascript/testdata/tokens/parameter.yaml new file mode 100644 index 0000000000..460363c393 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/parameter.yaml @@ -0,0 +1,9 @@ +expression: "{string}" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_PARAMETER", "start": 0, "end": 1, "text": "{"}, + {"type": "TEXT", "start": 1, "end": 7, "text": "string"}, + {"type": "END_PARAMETER", "start": 7, "end": 8, "text": "}"}, + {"type": "END_OF_LINE", "start": 8, "end": 8, "text": ""} + ] diff --git a/cucumber-expressions/javascript/testdata/tokens/phrase.yaml b/cucumber-expressions/javascript/testdata/tokens/phrase.yaml new file mode 100644 index 0000000000..e2cfccf7b4 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/tokens/phrase.yaml @@ -0,0 +1,11 @@ +expression: three blind mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "WHITE_SPACE", "start": 11, "end": 12, "text": " "}, + {"type": "TEXT", "start": 12, "end": 16, "text": "mice"}, + {"type": "END_OF_LINE", "start": 16, "end": 16, "text": ""} + ] From 0857cd18b710a0e0e82bb966c211254ae618d1d8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 20 Sep 2020 11:15:40 +0200 Subject: [PATCH 141/183] Clean up --- .../javascript/test/CucumberExpressionTokenizerTest.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts index 9b7afff1a5..3dae4c5b14 100644 --- a/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts +++ b/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts @@ -12,10 +12,9 @@ interface Expectation { describe('Cucumber expression tokenizer', () => { fs.readdirSync('testdata/tokens').forEach((testcase) => { + const testCaseData = fs.readFileSync(`testdata/tokens/${testcase}`, 'utf-8') + const expectation = yaml.safeLoad(testCaseData) as Expectation it(`${testcase}`, () => { - const expectation = yaml.safeLoad( - fs.readFileSync(`testdata/tokens/${testcase}`, 'utf-8') - ) as Expectation const tokenizer = new CucumberExpressionTokenizer() if (expectation.exception == undefined) { const tokens = tokenizer.tokenize(expectation.expression) From 8e446dd5cd381044efea74ba57ad5b4382f033d5 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 20 Sep 2020 11:41:27 +0200 Subject: [PATCH 142/183] Empty string passes --- cucumber-expressions/javascript/src/Ast.ts | 26 ++++++++++++++++--- .../src/CucumberExpressionTokenizer.ts | 7 +++-- .../test/CucumberExpressionTokenizerTest.ts | 5 +++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/cucumber-expressions/javascript/src/Ast.ts b/cucumber-expressions/javascript/src/Ast.ts index a0a1083b9f..c969985483 100644 --- a/cucumber-expressions/javascript/src/Ast.ts +++ b/cucumber-expressions/javascript/src/Ast.ts @@ -1,5 +1,25 @@ -export default class Token { - constructor() { - // TODO: +export class Token { + readonly type: TokenType + readonly text: string + readonly start: number + readonly end: number + + constructor(type: TokenType, text: string, start: number, end: number) { + this.type = type + this.text = text + this.start = start + this.end = end } } + +export enum TokenType { + startOfLine = 'START_OF_LINE', + endOfLine = 'END_OF_LINE', + whiteSpace = 'WHITE_SPACE', + beginOptional = 'BEGIN_OPTIONAL', + endOptional = 'END_OPTIONAL', + beginParameter = 'BEGIN_PARAMETER', + endParameter = 'END_PARAMETER', + alternation = 'ALTERNATION', + text = 'TEXT', +} diff --git a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts index 014f5dbcfc..37aa8153b8 100644 --- a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts +++ b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts @@ -1,4 +1,4 @@ -import Token from './Ast' +import { Token, TokenType } from './Ast' export default class CucumberExpressionTokenizer { constructor() { @@ -6,6 +6,9 @@ export default class CucumberExpressionTokenizer { } tokenize(expression: string): ReadonlyArray { - return [] + return [ + new Token(TokenType.startOfLine, '', 0, 0), + new Token(TokenType.endOfLine, '', 0, 0), + ] } } diff --git a/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts index 3dae4c5b14..5eb11ae61a 100644 --- a/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts +++ b/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts @@ -18,7 +18,10 @@ describe('Cucumber expression tokenizer', () => { const tokenizer = new CucumberExpressionTokenizer() if (expectation.exception == undefined) { const tokens = tokenizer.tokenize(expectation.expression) - assert.deepStrictEqual(tokens, JSON.parse(expectation.expected)) + assert.deepStrictEqual( + JSON.parse(JSON.stringify(tokens)), // Removes type information. + JSON.parse(expectation.expected) + ) } else { assert.throws(() => { tokenizer.tokenize(expectation.expression) From 3d2bea29cbccdf4b3f205eebb86b92c108259668 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 20 Sep 2020 19:19:16 +0200 Subject: [PATCH 143/183] All but the exceptional cases pass --- cucumber-expressions/javascript/src/Ast.ts | 53 +++++++++++ .../src/CucumberExpressionTokenizer.ts | 88 +++++++++++++++++-- cucumber-expressions/javascript/src/Errors.ts | 12 +++ .../test/CucumberExpressionTokenizerTest.ts | 3 +- 4 files changed, 147 insertions(+), 9 deletions(-) diff --git a/cucumber-expressions/javascript/src/Ast.ts b/cucumber-expressions/javascript/src/Ast.ts index c969985483..fe8f8304f0 100644 --- a/cucumber-expressions/javascript/src/Ast.ts +++ b/cucumber-expressions/javascript/src/Ast.ts @@ -1,3 +1,10 @@ +const escapeCharacter = '\\' +const alternationCharacter = '/' +const beginParameterCharacter = '{' +const endParameterCharacter = '}' +const beginOptionalCharacter = '(' +const endOptionalCharacter = ')' + export class Token { readonly type: TokenType readonly text: string @@ -10,6 +17,52 @@ export class Token { this.start = start this.end = end } + + static isEscapeCharacter(codePoint: string): boolean { + return codePoint == escapeCharacter + } + + static canEscape(codePoint: string): boolean { + if (codePoint == ' ') { + // TODO: Unicode whitespace? + return true + } + switch (codePoint) { + case escapeCharacter: + return true + case alternationCharacter: + return true + case beginParameterCharacter: + return true + case endParameterCharacter: + return true + case beginOptionalCharacter: + return true + case endOptionalCharacter: + return true + } + return false + } + + static typeOf(codePoint: string): TokenType { + if (codePoint == ' ') { + // TODO: Unicode whitespace? + return TokenType.whiteSpace + } + switch (codePoint) { + case alternationCharacter: + return TokenType.alternation + case beginParameterCharacter: + return TokenType.beginParameter + case endParameterCharacter: + return TokenType.endParameter + case beginOptionalCharacter: + return TokenType.beginOptional + case endOptionalCharacter: + return TokenType.endOptional + } + return TokenType.text + } } export enum TokenType { diff --git a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts index 37aa8153b8..40bbdea46c 100644 --- a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts +++ b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts @@ -1,14 +1,86 @@ import { Token, TokenType } from './Ast' +import { createCantEscaped, createTheEndOfLIneCanNotBeEscaped } from './Errors' export default class CucumberExpressionTokenizer { - constructor() { - // TODO: - } - tokenize(expression: string): ReadonlyArray { - return [ - new Token(TokenType.startOfLine, '', 0, 0), - new Token(TokenType.endOfLine, '', 0, 0), - ] + const codePoints = Array.from(expression) + const tokens: Array = [] + let buffer: Array = [] + let previousTokenType = TokenType.startOfLine + let treatAsText = false + let escaped = 0 + let bufferStartIndex = 0 + + function convertBufferToToken(tokenType: TokenType): Token { + let escapeTokens = 0 + if (tokenType == TokenType.text) { + escapeTokens = escaped + escaped = 0 + } + + const consumedIndex = bufferStartIndex + buffer.length + escapeTokens + const t = new Token( + tokenType, + buffer.join(''), + bufferStartIndex, + consumedIndex + ) + buffer = [] + bufferStartIndex = consumedIndex + return t + } + + function tokenTypeOf(codePoint: string, treatAsText: boolean): TokenType { + if (!treatAsText) { + return Token.typeOf(codePoint) + } + if (Token.canEscape(codePoint)) { + return TokenType.text + } + throw createCantEscaped( + expression, + bufferStartIndex + buffer.length + escaped + ) + } + + tokens.push(new Token(TokenType.startOfLine, '', 0, 0)) + + codePoints.forEach((codePoint) => { + if (!treatAsText && Token.isEscapeCharacter(codePoint)) { + escaped++ + treatAsText = true + return + } + const currentTokenType = tokenTypeOf(codePoint, treatAsText) + treatAsText = false + if ( + previousTokenType != TokenType.startOfLine && + (currentTokenType != previousTokenType || + (currentTokenType != TokenType.whiteSpace && + currentTokenType != TokenType.text)) + ) { + const token = convertBufferToToken(previousTokenType) + previousTokenType = currentTokenType + buffer.push(codePoint) + tokens.push(token) + } else { + previousTokenType = currentTokenType + buffer.push(codePoint) + } + }) + + if (buffer.length > 0) { + const token = convertBufferToToken(previousTokenType) + tokens.push(token) + } + + if (treatAsText) { + throw createTheEndOfLIneCanNotBeEscaped(expression) + } + + tokens.push( + new Token(TokenType.endOfLine, '', codePoints.length, codePoints.length) + ) + return tokens } } diff --git a/cucumber-expressions/javascript/src/Errors.ts b/cucumber-expressions/javascript/src/Errors.ts index 2dedafb57f..6161f76d45 100644 --- a/cucumber-expressions/javascript/src/Errors.ts +++ b/cucumber-expressions/javascript/src/Errors.ts @@ -3,6 +3,16 @@ import GeneratedExpression from './GeneratedExpression' class CucumberExpressionError extends Error {} +function createTheEndOfLIneCanNotBeEscaped( + expression: string +): CucumberExpressionError { + return new CucumberExpressionError(expression) +} + +function createCantEscaped(expression: string, number: number) { + return new CucumberExpressionError(expression) +} + class AmbiguousParameterTypeError extends CucumberExpressionError { public static forConstructor( keyName: string, @@ -59,4 +69,6 @@ export { AmbiguousParameterTypeError, UndefinedParameterTypeError, CucumberExpressionError, + createTheEndOfLIneCanNotBeEscaped, + createCantEscaped, } diff --git a/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts index 5eb11ae61a..2d55174495 100644 --- a/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts +++ b/cucumber-expressions/javascript/test/CucumberExpressionTokenizerTest.ts @@ -3,6 +3,7 @@ import fs from 'fs' import yaml from 'js-yaml' // why? import CucumberExpressionTokenizer from '../src/CucumberExpressionTokenizer' import assert from 'assert' +import { CucumberExpressionError } from '../src/Errors' interface Expectation { expression: string @@ -25,7 +26,7 @@ describe('Cucumber expression tokenizer', () => { } else { assert.throws(() => { tokenizer.tokenize(expectation.expression) - }, expectation.exception) + }, new CucumberExpressionError(expectation.exception)) } }) }) From 3bd6ab3214461bb573c59e3f9313356d21eb76f1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 20 Sep 2020 19:40:45 +0200 Subject: [PATCH 144/183] All tokenizer cases pass --- cucumber-expressions/javascript/src/Errors.ts | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/javascript/src/Errors.ts b/cucumber-expressions/javascript/src/Errors.ts index 6161f76d45..3e723e50ab 100644 --- a/cucumber-expressions/javascript/src/Errors.ts +++ b/cucumber-expressions/javascript/src/Errors.ts @@ -6,11 +6,52 @@ class CucumberExpressionError extends Error {} function createTheEndOfLIneCanNotBeEscaped( expression: string ): CucumberExpressionError { - return new CucumberExpressionError(expression) + const index = Array.from(expression).length - 1 + return new CucumberExpressionError( + message( + index, + expression, + pointAt(index), + 'The end of line can not be escaped', + "You can use '\\\\' to escape the the '\\'" + ) + ) +} + +function createCantEscaped(expression: string, index: number) { + return new CucumberExpressionError( + message( + index, + expression, + pointAt(index), + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped", + "If you did mean to use an '\\' you can use '\\\\' to escape it" + ) + ) } -function createCantEscaped(expression: string, number: number) { - return new CucumberExpressionError(expression) +function message( + index: number, + expression: string, + pointer: any, + problem: string, + solution: string +): string { + return `This Cucumber Expression has a problem at column ${index + 1}: + +${expression} +${pointer} +${problem}. +${solution}` +} + +function pointAt(index: number): string { + const pointer: Array = [] + for (let i = 0; i < index; i++) { + pointer.push(' ') + } + pointer.push('^') + return pointer.join('') } class AmbiguousParameterTypeError extends CucumberExpressionError { From 95c5f449852b2378a78d193e66130129639b01df Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 20 Sep 2020 19:57:54 +0200 Subject: [PATCH 145/183] Make the code look the same --- .../go/cucumber_expression_tokenizer.go | 10 ++- .../CucumberExpressionTokenizer.java | 72 ++++++++++--------- .../src/CucumberExpressionTokenizer.ts | 18 ++++- 3 files changed, 63 insertions(+), 37 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 6216af47dc..079e66aa65 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -35,6 +35,13 @@ func tokenize(expression string) ([]token, error) { return startOfLine, createCantEscaped(expression, bufferStartIndex+len(buffer)+escaped) } + shouldCreateNewToken := func(currentTokenType tokenType, previousTokenType tokenType) bool { + if currentTokenType != previousTokenType { + return true + } + return currentTokenType != whiteSpace && currentTokenType != text + } + tokens = append(tokens, token{"", startOfLine, 0, 0}) for _, r := range runes { @@ -50,7 +57,7 @@ func tokenize(expression string) ([]token, error) { } treatAsText = false - if previousTokenType != startOfLine && (currentTokenType != previousTokenType || (currentTokenType != whiteSpace && currentTokenType != text)) { + if previousTokenType != startOfLine && shouldCreateNewToken(currentTokenType, previousTokenType) { token := convertBufferToToken(previousTokenType) previousTokenType = currentTokenType buffer = append(buffer, r) @@ -73,3 +80,4 @@ func tokenize(expression string) ([]token, error) { tokens = append(tokens, token) return tokens, nil } + diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java index bf3349a83d..9a2a9df254 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionTokenizer.java @@ -41,6 +41,40 @@ private static class TokenIterator implements Iterator { this.codePoints = expression.codePoints().iterator(); } + private Token convertBufferToToken(Type tokenType) { + int escapeTokens = 0; + if (tokenType == Type.TEXT) { + escapeTokens = escaped; + escaped = 0; + } + int consumedIndex = bufferStartIndex + buffer.codePointCount(0, buffer.length()) + escapeTokens; + Token t = new Token(buffer.toString(), tokenType, bufferStartIndex, consumedIndex); + buffer = new StringBuilder(); + this.bufferStartIndex = consumedIndex; + return t; + } + + private void advanceTokenTypes() { + previousTokenType = currentTokenType; + currentTokenType = null; + } + + private Type tokenTypeOf(Integer token, boolean treatAsText) { + if (!treatAsText) { + return Token.typeOf(token); + } + if (Token.canEscape(token)) { + return Type.TEXT; + } + throw createCantEscape(expression, bufferStartIndex + buffer.codePointCount(0, buffer.length()) + escaped); + } + + private boolean shouldContinueTokenType(Type previousTokenType, + Type currentTokenType) { + return currentTokenType == previousTokenType + && (currentTokenType == Type.WHITE_SPACE || currentTokenType == Type.TEXT); + } + @Override public boolean hasNext() { return previousTokenType != Type.END_OF_LINE; @@ -67,11 +101,11 @@ public Token next() { currentTokenType = tokenTypeOf(codePoint, treatAsText); treatAsText = false; - if (previousTokenType == Type.START_OF_LINE || (currentTokenType == previousTokenType - && (currentTokenType == Type.WHITE_SPACE || currentTokenType == Type.TEXT))) { - advanceTokenTypes(); - buffer.appendCodePoint(codePoint); - } else { + if (previousTokenType == Type.START_OF_LINE || + shouldContinueTokenType(previousTokenType, currentTokenType)) { + advanceTokenTypes(); + buffer.appendCodePoint(codePoint); + } else { Token t = convertBufferToToken(previousTokenType); advanceTokenTypes(); buffer.appendCodePoint(codePoint); @@ -94,34 +128,6 @@ public Token next() { return token; } - private void advanceTokenTypes() { - previousTokenType = currentTokenType; - currentTokenType = null; - } - - private Token convertBufferToToken(Type tokenType) { - int escapeTokens = 0; - if (tokenType == Type.TEXT) { - escapeTokens = escaped; - escaped = 0; - } - int consumedIndex = bufferStartIndex + buffer.codePointCount(0, buffer.length()) + escapeTokens; - Token t = new Token(buffer.toString(), tokenType, bufferStartIndex, consumedIndex); - buffer = new StringBuilder(); - this.bufferStartIndex = consumedIndex; - return t; - } - - private Type tokenTypeOf(Integer token, boolean treatAsText) { - if (!treatAsText) { - return Token.typeOf(token); - } - if (Token.canEscape(token)) { - return Type.TEXT; - } - throw createCantEscape(expression, bufferStartIndex + buffer.codePointCount(0, buffer.length()) + escaped); - } - } } diff --git a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts index 40bbdea46c..973fa6b9b8 100644 --- a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts +++ b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts @@ -43,6 +43,19 @@ export default class CucumberExpressionTokenizer { ) } + function shouldCreateNewToken( + previousTokenType: TokenType, + currentTokenType: TokenType + ) { + if (currentTokenType != previousTokenType) { + return true + } + return ( + currentTokenType != TokenType.whiteSpace && + currentTokenType != TokenType.text + ) + } + tokens.push(new Token(TokenType.startOfLine, '', 0, 0)) codePoints.forEach((codePoint) => { @@ -53,11 +66,10 @@ export default class CucumberExpressionTokenizer { } const currentTokenType = tokenTypeOf(codePoint, treatAsText) treatAsText = false + if ( previousTokenType != TokenType.startOfLine && - (currentTokenType != previousTokenType || - (currentTokenType != TokenType.whiteSpace && - currentTokenType != TokenType.text)) + shouldCreateNewToken(previousTokenType, currentTokenType) ) { const token = convertBufferToToken(previousTokenType) previousTokenType = currentTokenType From 20d40310f018215784b5692fa765ead9a6e35e1b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 20 Sep 2020 20:05:50 +0200 Subject: [PATCH 146/183] Simplify new token creation condition --- cucumber-expressions/go/cucumber_expression_tokenizer.go | 6 ++++-- .../javascript/src/CucumberExpressionTokenizer.ts | 9 ++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 079e66aa65..34f6f40d79 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -42,7 +42,9 @@ func tokenize(expression string) ([]token, error) { return currentTokenType != whiteSpace && currentTokenType != text } - tokens = append(tokens, token{"", startOfLine, 0, 0}) + if len(runes) == 0 { + tokens = append(tokens, token{"", startOfLine, 0, 0}) + } for _, r := range runes { if !treatAsText && isEscapeCharacter(r) { @@ -57,7 +59,7 @@ func tokenize(expression string) ([]token, error) { } treatAsText = false - if previousTokenType != startOfLine && shouldCreateNewToken(currentTokenType, previousTokenType) { + if shouldCreateNewToken(currentTokenType, previousTokenType) { token := convertBufferToToken(previousTokenType) previousTokenType = currentTokenType buffer = append(buffer, r) diff --git a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts index 973fa6b9b8..8853f39043 100644 --- a/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts +++ b/cucumber-expressions/javascript/src/CucumberExpressionTokenizer.ts @@ -56,7 +56,9 @@ export default class CucumberExpressionTokenizer { ) } - tokens.push(new Token(TokenType.startOfLine, '', 0, 0)) + if (codePoints.length == 0) { + tokens.push(new Token(TokenType.startOfLine, '', 0, 0)) + } codePoints.forEach((codePoint) => { if (!treatAsText && Token.isEscapeCharacter(codePoint)) { @@ -67,10 +69,7 @@ export default class CucumberExpressionTokenizer { const currentTokenType = tokenTypeOf(codePoint, treatAsText) treatAsText = false - if ( - previousTokenType != TokenType.startOfLine && - shouldCreateNewToken(previousTokenType, currentTokenType) - ) { + if (shouldCreateNewToken(previousTokenType, currentTokenType)) { const token = convertBufferToToken(previousTokenType) previousTokenType = currentTokenType buffer.push(codePoint) From ddb8b0555322629723e7ce20b6d66017e70d9365 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 21 Sep 2020 16:48:49 +0200 Subject: [PATCH 147/183] Add parser test. Empty string passes --- cucumber-expressions/javascript/src/Ast.ts | 37 +++++++++++++++++++ .../src/CucumberExpressionParser.ts | 10 +++++ .../test/CucumberExpressionParserTest.ts | 33 +++++++++++++++++ .../ast/alternation-followed-by-optional.yaml | 17 +++++++++ .../testdata/ast/alternation-phrase.yaml | 16 ++++++++ .../ast/alternation-with-parameter.yaml | 27 ++++++++++++++ .../alternation-with-unused-end-optional.yaml | 15 ++++++++ ...lternation-with-unused-start-optional.yaml | 8 ++++ .../ast/alternation-with-white-space.yaml | 12 ++++++ .../javascript/testdata/ast/alternation.yaml | 12 ++++++ .../testdata/ast/anonymous-parameter.yaml | 5 +++ .../testdata/ast/closing-brace.yaml | 5 +++ .../testdata/ast/closing-parenthesis.yaml | 5 +++ .../testdata/ast/empty-alternation.yaml | 8 ++++ .../testdata/ast/empty-alternations.yaml | 9 +++++ .../javascript/testdata/ast/empty-string.yaml | 3 ++ .../testdata/ast/escaped-alternation.yaml | 5 +++ .../testdata/ast/escaped-backslash.yaml | 5 +++ .../ast/escaped-opening-parenthesis.yaml | 5 +++ ...escaped-optional-followed-by-optional.yaml | 15 ++++++++ .../testdata/ast/escaped-optional-phrase.yaml | 10 +++++ .../testdata/ast/escaped-optional.yaml | 7 ++++ .../testdata/ast/opening-brace.yaml | 8 ++++ .../testdata/ast/opening-parenthesis.yaml | 8 ++++ .../optional-containing-escaped-optional.yaml | 14 +++++++ .../testdata/ast/optional-phrase.yaml | 12 ++++++ .../javascript/testdata/ast/optional.yaml | 7 ++++ .../javascript/testdata/ast/parameter.yaml | 7 ++++ .../javascript/testdata/ast/phrase.yaml | 9 +++++ .../testdata/ast/unfinished-parameter.yaml | 8 ++++ 30 files changed, 342 insertions(+) create mode 100644 cucumber-expressions/javascript/src/CucumberExpressionParser.ts create mode 100644 cucumber-expressions/javascript/test/CucumberExpressionParserTest.ts create mode 100644 cucumber-expressions/javascript/testdata/ast/alternation-followed-by-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/alternation-phrase.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/alternation-with-parameter.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/alternation-with-unused-end-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/alternation-with-unused-start-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/alternation-with-white-space.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/alternation.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/anonymous-parameter.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/closing-brace.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/closing-parenthesis.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/empty-alternation.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/empty-alternations.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/empty-string.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/escaped-alternation.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/escaped-backslash.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/escaped-opening-parenthesis.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/escaped-optional-followed-by-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/escaped-optional-phrase.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/escaped-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/opening-brace.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/opening-parenthesis.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/optional-phrase.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/parameter.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/phrase.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/unfinished-parameter.yaml diff --git a/cucumber-expressions/javascript/src/Ast.ts b/cucumber-expressions/javascript/src/Ast.ts index fe8f8304f0..51444c02cd 100644 --- a/cucumber-expressions/javascript/src/Ast.ts +++ b/cucumber-expressions/javascript/src/Ast.ts @@ -5,6 +5,43 @@ const endParameterCharacter = '}' const beginOptionalCharacter = '(' const endOptionalCharacter = ')' +export class Node { + readonly type: NodeType + readonly nodes?: ReadonlyArray | undefined + readonly token?: string | undefined + private start: number + private end: number + + constructor( + type: NodeType, + nodes: ReadonlyArray = undefined, + token: string = undefined, + start: number, + end: number + ) { + if (nodes === undefined && token === undefined) { + throw new Error('Either nodes or token must be defined') + } + if (nodes === null || token === null) { + throw new Error('Either nodes or token may not be null') + } + this.type = type + this.nodes = nodes + this.token = token + this.start = start + this.end = end + } +} + +export enum NodeType { + textNode = 'TEXT_NODE', + optionalNode = 'OPTIONAL_NODE', + alternationNode = 'ALTERNATION_NODE', + alternativeNode = 'ALTERNATIVE_NODE', + parameterNode = 'PARAMETER_NODE', + expressionNode = 'EXPRESSION_NODE', +} + export class Token { readonly type: TokenType readonly text: string diff --git a/cucumber-expressions/javascript/src/CucumberExpressionParser.ts b/cucumber-expressions/javascript/src/CucumberExpressionParser.ts new file mode 100644 index 0000000000..5c2aafaada --- /dev/null +++ b/cucumber-expressions/javascript/src/CucumberExpressionParser.ts @@ -0,0 +1,10 @@ +import { Node, NodeType } from './Ast' +import CucumberExpressionTokenizer from './CucumberExpressionTokenizer' + +export default class CucumberExpressionParser { + parse(expression: string): Node { + const tokenizer = new CucumberExpressionTokenizer() + const tokens = tokenizer.tokenize(expression) + return new Node(NodeType.expressionNode, [], undefined, 0, 0) + } +} diff --git a/cucumber-expressions/javascript/test/CucumberExpressionParserTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionParserTest.ts new file mode 100644 index 0000000000..bfe560fe79 --- /dev/null +++ b/cucumber-expressions/javascript/test/CucumberExpressionParserTest.ts @@ -0,0 +1,33 @@ +import fs from 'fs' +// eslint-disable-next-line node/no-extraneous-import +import yaml from 'js-yaml' // why? +import assert from 'assert' +import { CucumberExpressionError } from '../src/Errors' +import CucumberExpressionParser from '../src/CucumberExpressionParser' + +interface Expectation { + expression: string + expected?: string + exception?: string +} + +describe('Cucumber expression parser', () => { + fs.readdirSync('testdata/ast').forEach((testcase) => { + const testCaseData = fs.readFileSync(`testdata/ast/${testcase}`, 'utf-8') + const expectation = yaml.safeLoad(testCaseData) as Expectation + it(`${testcase}`, () => { + const parser = new CucumberExpressionParser() + if (expectation.exception == undefined) { + const node = parser.parse(expectation.expression) + assert.deepStrictEqual( + JSON.parse(JSON.stringify(node)), // Removes type information. + JSON.parse(expectation.expected) + ) + } else { + assert.throws(() => { + parser.parse(expectation.expression) + }, new CucumberExpressionError(expectation.exception)) + } + }) + }) +}) diff --git a/cucumber-expressions/javascript/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/javascript/testdata/ast/alternation-followed-by-optional.yaml new file mode 100644 index 0000000000..0bfb53a534 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/alternation-followed-by-optional.yaml @@ -0,0 +1,17 @@ +expression: three blind\ rat/cat(s) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 16, "token": "blind rat"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 17, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 17, "end": 20, "token": "cat"}, + {"type": "OPTIONAL_NODE", "start": 20, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": "s"} + ]} + ]} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/javascript/testdata/ast/alternation-phrase.yaml new file mode 100644 index 0000000000..9a243822d5 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/alternation-phrase.yaml @@ -0,0 +1,16 @@ +expression: three hungry/blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 12, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 12, "token": "hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 13, "end": 18, "token": "blind"} + ]} + ]}, + {"type": "TEXT_NODE", "start": 18, "end": 19, "token": " "}, + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "mice"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/alternation-with-parameter.yaml b/cucumber-expressions/javascript/testdata/ast/alternation-with-parameter.yaml new file mode 100644 index 0000000000..c5daf32bd7 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/alternation-with-parameter.yaml @@ -0,0 +1,27 @@ +expression: I select the {int}st/nd/rd/th +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "I"}, + {"type": "TEXT_NODE", "start": 1, "end": 2, "token": " "}, + {"type": "TEXT_NODE", "start": 2, "end": 8, "token": "select"}, + {"type": "TEXT_NODE", "start": 8, "end": 9, "token": " "}, + {"type": "TEXT_NODE", "start": 9, "end": 12, "token": "the"}, + {"type": "TEXT_NODE", "start": 12, "end": 13, "token": " "}, + {"type": "PARAMETER_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 14, "end": 17, "token": "int"} + ]}, + {"type": "ALTERNATION_NODE", "start": 18, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 18, "end": 20, "nodes": [ + {"type": "TEXT_NODE", "start": 18, "end": 20, "token": "st"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 21, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 23, "token": "nd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 24, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 24, "end": 26, "token": "rd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 27, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 27, "end": 29, "token": "th"} + ]} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/javascript/testdata/ast/alternation-with-unused-end-optional.yaml new file mode 100644 index 0000000000..842838b75f --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/alternation-with-unused-end-optional.yaml @@ -0,0 +1,15 @@ +expression: three )blind\ mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 7, "token": ")"}, + {"type": "TEXT_NODE", "start": 7, "end": 18, "token": "blind mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 19, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/alternation-with-unused-start-optional.yaml b/cucumber-expressions/javascript/testdata/ast/alternation-with-unused-start-optional.yaml new file mode 100644 index 0000000000..e2f0584556 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/alternation-with-unused-start-optional.yaml @@ -0,0 +1,8 @@ +expression: three blind\ mice/rats( +exception: |- + This Cucumber Expression has a problem at column 23: + + three blind\ mice/rats( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/javascript/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/javascript/testdata/ast/alternation-with-white-space.yaml new file mode 100644 index 0000000000..eedd57dd21 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/alternation-with-white-space.yaml @@ -0,0 +1,12 @@ +expression: '\ three\ hungry/blind\ mice\ ' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 15, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 15, "token": " three hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 16, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 16, "end": 29, "token": "blind mice "} + ]} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/alternation.yaml b/cucumber-expressions/javascript/testdata/ast/alternation.yaml new file mode 100644 index 0000000000..88df8325fe --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/alternation.yaml @@ -0,0 +1,12 @@ +expression: mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 4, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 4, "token": "mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 5, "end": 9, "nodes": [ + {"type": "TEXT_NODE", "start": 5, "end": 9, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/javascript/testdata/ast/anonymous-parameter.yaml new file mode 100644 index 0000000000..2c4d339333 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/anonymous-parameter.yaml @@ -0,0 +1,5 @@ +expression: "{}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 2, "nodes": []} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/closing-brace.yaml b/cucumber-expressions/javascript/testdata/ast/closing-brace.yaml new file mode 100644 index 0000000000..1bafd9c6a8 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/closing-brace.yaml @@ -0,0 +1,5 @@ +expression: "}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "}"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/javascript/testdata/ast/closing-parenthesis.yaml new file mode 100644 index 0000000000..23daf7bcd3 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/closing-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: ) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": ")"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/empty-alternation.yaml b/cucumber-expressions/javascript/testdata/ast/empty-alternation.yaml new file mode 100644 index 0000000000..6d810fc8f3 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/empty-alternation.yaml @@ -0,0 +1,8 @@ +expression: / +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/empty-alternations.yaml b/cucumber-expressions/javascript/testdata/ast/empty-alternations.yaml new file mode 100644 index 0000000000..f8d4dd4cf8 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/empty-alternations.yaml @@ -0,0 +1,9 @@ +expression: '//' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/empty-string.yaml b/cucumber-expressions/javascript/testdata/ast/empty-string.yaml new file mode 100644 index 0000000000..4d33c2dc76 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/empty-string.yaml @@ -0,0 +1,3 @@ +expression: "" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 0, "nodes": []} diff --git a/cucumber-expressions/javascript/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/javascript/testdata/ast/escaped-alternation.yaml new file mode 100644 index 0000000000..3ed9c37674 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/escaped-alternation.yaml @@ -0,0 +1,5 @@ +expression: 'mice\/rats' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 10, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 10, "token": "mice/rats"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/javascript/testdata/ast/escaped-backslash.yaml new file mode 100644 index 0000000000..da2d008e1e --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/escaped-backslash.yaml @@ -0,0 +1,5 @@ +expression: '\\' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\\"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/javascript/testdata/ast/escaped-opening-parenthesis.yaml new file mode 100644 index 0000000000..afafc59eb8 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/escaped-opening-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: '\(' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "("} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/javascript/testdata/ast/escaped-optional-followed-by-optional.yaml new file mode 100644 index 0000000000..1e4746291b --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -0,0 +1,15 @@ +expression: three \((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 8, "token": "("}, + {"type": "OPTIONAL_NODE", "start": 8, "end": 14, "nodes": [ + {"type": "TEXT_NODE", "start": 9, "end": 13, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": ")"}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/javascript/testdata/ast/escaped-optional-phrase.yaml new file mode 100644 index 0000000000..832249e2a7 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/escaped-optional-phrase.yaml @@ -0,0 +1,10 @@ +expression: three \(blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 19, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 13, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": ")"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 19, "token": "mice"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/escaped-optional.yaml b/cucumber-expressions/javascript/testdata/ast/escaped-optional.yaml new file mode 100644 index 0000000000..4c2b457d6f --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: '\(blind)' + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 7, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": ")"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/opening-brace.yaml b/cucumber-expressions/javascript/testdata/ast/opening-brace.yaml new file mode 100644 index 0000000000..916a674a36 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/opening-brace.yaml @@ -0,0 +1,8 @@ +expression: '{' +exception: |- + This Cucumber Expression has a problem at column 1: + + { + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/javascript/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/javascript/testdata/ast/opening-parenthesis.yaml new file mode 100644 index 0000000000..929d6ae304 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/opening-parenthesis.yaml @@ -0,0 +1,8 @@ +expression: ( +exception: |- + This Cucumber Expression has a problem at column 1: + + ( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/javascript/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/javascript/testdata/ast/optional-containing-escaped-optional.yaml new file mode 100644 index 0000000000..f09199a454 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/optional-containing-escaped-optional.yaml @@ -0,0 +1,14 @@ +expression: three ((very\) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, + {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/optional-phrase.yaml b/cucumber-expressions/javascript/testdata/ast/optional-phrase.yaml new file mode 100644 index 0000000000..0ef4fb3f95 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/optional-phrase.yaml @@ -0,0 +1,12 @@ +expression: three (blind) mice + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 12, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 18, "token": "mice"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/optional.yaml b/cucumber-expressions/javascript/testdata/ast/optional.yaml new file mode 100644 index 0000000000..6ce2b632e7 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/optional.yaml @@ -0,0 +1,7 @@ +expression: (blind) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 6, "token": "blind"} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/parameter.yaml b/cucumber-expressions/javascript/testdata/ast/parameter.yaml new file mode 100644 index 0000000000..92ac8c147e --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/parameter.yaml @@ -0,0 +1,7 @@ +expression: "{string}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 7, "token": "string"} + ]} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/phrase.yaml b/cucumber-expressions/javascript/testdata/ast/phrase.yaml new file mode 100644 index 0000000000..ba340d0122 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/phrase.yaml @@ -0,0 +1,9 @@ +expression: three blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 11, "token": "blind"}, + {"type": "TEXT_NODE", "start": 11, "end": 12, "token": " "}, + {"type": "TEXT_NODE", "start": 12, "end": 16, "token": "mice"} + ]} diff --git a/cucumber-expressions/javascript/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/javascript/testdata/ast/unfinished-parameter.yaml new file mode 100644 index 0000000000..d02f9b4ccf --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/unfinished-parameter.yaml @@ -0,0 +1,8 @@ +expression: "{string" +exception: |- + This Cucumber Expression has a problem at column 1: + + {string + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter From d2f638d65276688ac017a7a52a8dfbd1a59c7379 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Sep 2020 14:04:13 +0200 Subject: [PATCH 148/183] Parse all the other expressions --- .../CucumberExpressionParser.java | 4 +- cucumber-expressions/javascript/src/Ast.ts | 16 +- .../src/CucumberExpressionParser.ts | 308 +++++++++++++++++- 3 files changed, 317 insertions(+), 11 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 8bf6ef636f..8912d9af73 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -57,7 +57,9 @@ final class CucumberExpressionParser { asList(parameterParser, textParser) ); - // alternation := alternative* + ( '/' + alternative* )+ + /* + * alternation := alternative* + ( '/' + alternative* )+ + */ private static final Parser alternativeSeparator = (expression, tokens, current) -> { if (!lookingAt(tokens, current, ALTERNATION)) { return new Result(0); diff --git a/cucumber-expressions/javascript/src/Ast.ts b/cucumber-expressions/javascript/src/Ast.ts index 51444c02cd..69f989f1ea 100644 --- a/cucumber-expressions/javascript/src/Ast.ts +++ b/cucumber-expressions/javascript/src/Ast.ts @@ -9,8 +9,8 @@ export class Node { readonly type: NodeType readonly nodes?: ReadonlyArray | undefined readonly token?: string | undefined - private start: number - private end: number + readonly start: number + readonly end: number constructor( type: NodeType, @@ -34,12 +34,12 @@ export class Node { } export enum NodeType { - textNode = 'TEXT_NODE', - optionalNode = 'OPTIONAL_NODE', - alternationNode = 'ALTERNATION_NODE', - alternativeNode = 'ALTERNATIVE_NODE', - parameterNode = 'PARAMETER_NODE', - expressionNode = 'EXPRESSION_NODE', + text = 'TEXT_NODE', + optional = 'OPTIONAL_NODE', + alternation = 'ALTERNATION_NODE', + alternative = 'ALTERNATIVE_NODE', + parameter = 'PARAMETER_NODE', + expression = 'EXPRESSION_NODE', } export class Token { diff --git a/cucumber-expressions/javascript/src/CucumberExpressionParser.ts b/cucumber-expressions/javascript/src/CucumberExpressionParser.ts index 5c2aafaada..22312dc17c 100644 --- a/cucumber-expressions/javascript/src/CucumberExpressionParser.ts +++ b/cucumber-expressions/javascript/src/CucumberExpressionParser.ts @@ -1,10 +1,314 @@ -import { Node, NodeType } from './Ast' +import { Node, NodeType, Token, TokenType } from './Ast' import CucumberExpressionTokenizer from './CucumberExpressionTokenizer' +/* + * text := token + */ +function parseText( + expression: string, + tokens: ReadonlyArray, + current: number +) { + const token = tokens[current] + return { + consumed: 1, + ast: [ + new Node(NodeType.text, undefined, token.text, token.start, token.end), + ], + } +} + +/* + * parameter := '{' + text* + '}' + */ +const parseParameter = parseBetween( + NodeType.parameter, + TokenType.beginParameter, + TokenType.endParameter, + [parseText] +) + +/* + * optional := '(' + option* + ')' + * option := parameter | text + */ +const parseOptional = parseBetween( + NodeType.optional, + TokenType.beginOptional, + TokenType.endOptional, + [parseParameter, parseText] +) + +/* + * alternation := alternative* + ( '/' + alternative* )+ + */ +function parseAlternativeSeparator( + expression: string, + tokens: ReadonlyArray, + current: number +) { + if (!lookingAt(tokens, current, TokenType.alternation)) { + return { consumed: 0 } + } + const token = tokens[current] + return { + consumed: 1, + ast: [ + new Node( + NodeType.alternative, + undefined, + token.text, + token.start, + token.end + ), + ], + } +} + +const alternativeParsers: ReadonlyArray = [ + parseAlternativeSeparator, + parseOptional, + parseParameter, + parseText, +] + +/* + * alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary) + * left-boundary := whitespace | } | ^ + * right-boundary := whitespace | { | $ + * alternative: = optional | parameter | text + */ +const parseAlternation: Parser = (expression, tokens, current) => { + const previous = current - 1 + if ( + !lookingAtAny(tokens, previous, [ + TokenType.startOfLine, + TokenType.whiteSpace, + TokenType.endParameter, + ]) + ) { + return { consumed: 0 } + } + + const result = parseTokensUntil( + expression, + alternativeParsers, + tokens, + current, + [TokenType.whiteSpace, TokenType.endOfLine, TokenType.beginParameter] + ) + const subCurrent = current + result.consumed + if (!result.ast.some((astNode) => astNode.type == NodeType.alternative)) { + return { consumed: 0 } + } + + const start = tokens[current].start + const end = tokens[subCurrent].start + // Does not consume right hand boundary token + return { + consumed: result.consumed, + ast: [ + new Node( + NodeType.alternation, + splitAlternatives(start, end, result.ast), + undefined, + start, + end + ), + ], + } +} + +/* + * cucumber-expression := ( alternation | optional | parameter | text )* + */ +const parseCucumberExpression = parseBetween( + NodeType.expression, + TokenType.startOfLine, + TokenType.endOfLine, + [parseAlternation, parseOptional, parseParameter, parseText] +) + export default class CucumberExpressionParser { parse(expression: string): Node { const tokenizer = new CucumberExpressionTokenizer() const tokens = tokenizer.tokenize(expression) - return new Node(NodeType.expressionNode, [], undefined, 0, 0) + const result = parseCucumberExpression(expression, tokens, 0) + return result.ast[0] + } +} + +interface Parser { + (expression: string, tokens: ReadonlyArray, current: number): Result +} + +interface Result { + readonly consumed: number + readonly ast?: ReadonlyArray +} + +function parseBetween( + type: NodeType, + beginToken: TokenType, + endToken: TokenType, + parsers: ReadonlyArray +): Parser { + return (expression, tokens, current) => { + if (!lookingAt(tokens, current, beginToken)) { + return { consumed: 0 } + } + let subCurrent = current + 1 + const result = parseTokensUntil(expression, parsers, tokens, subCurrent, [ + endToken, + ]) + subCurrent += result.consumed + + // endToken not found + if (!lookingAt(tokens, subCurrent, endToken)) { + // throw createMissingEndToken( + // expression, + // beginToken, + // endToken, + // tokens[current] + // ) + throw new Error('TODO: createMissingEndToken') + } + // consumes endToken + const start = tokens[current].start + const end = tokens[subCurrent].end + const consumed = subCurrent + 1 - current + const ast = [new Node(type, result.ast, undefined, start, end)] + return { consumed, ast } + } +} + +function parseToken( + expression: string, + parsers: ReadonlyArray, + tokens: ReadonlyArray, + startAt: number +): Result { + for (let i = 0; i < parsers.length; i++) { + const parse = parsers[i] + const result = parse(expression, tokens, startAt) + if (result.consumed != 0) { + return result + } + } + // If configured correctly this will never happen + throw new Error('No eligible parsers for ' + tokens) +} + +function parseTokensUntil( + expression: string, + parsers: ReadonlyArray, + tokens: ReadonlyArray, + startAt: number, + endTokens: ReadonlyArray +): Result { + let current = startAt + const size = tokens.length + const ast: Node[] = [] + while (current < size) { + if (lookingAtAny(tokens, current, endTokens)) { + break + } + const result = parseToken(expression, parsers, tokens, current) + if (result.consumed == 0) { + // If configured correctly this will never happen + // Keep to avoid infinite loops + throw new Error('No eligible parsers for ' + tokens) + } + current += result.consumed + ast.push(...result.ast) + } + return { consumed: current - startAt, ast } +} + +function lookingAtAny( + tokens: ReadonlyArray, + at: number, + tokenTypes: ReadonlyArray +): boolean { + return tokenTypes.some((tokenType) => lookingAt(tokens, at, tokenType)) +} + +function lookingAt( + tokens: ReadonlyArray, + at: number, + token: TokenType +): boolean { + if (at < 0) { + // If configured correctly this will never happen + // Keep for completeness + return token == TokenType.startOfLine + } + if (at >= tokens.length) { + return token == TokenType.endOfLine + } + return tokens[at].type == token +} + +function splitAlternatives( + start: number, + end: number, + alternation: ReadonlyArray +): ReadonlyArray { + const separators: Node[] = [] + const alternatives: Node[][] = [] + let alternative: Node[] = [] + alternation.forEach((n) => { + if (NodeType.alternative == n.type) { + separators.push(n) + alternatives.push(alternative) + alternative = [] + } else { + alternative.push(n) + } + }) + alternatives.push(alternative) + return createAlternativeNodes(start, end, separators, alternatives) +} + +function createAlternativeNodes( + start: number, + end: number, + separators: ReadonlyArray, + alternatives: ReadonlyArray> +): ReadonlyArray { + const nodes: Node[] = [] + + for (let i = 0; i < alternatives.length; i++) { + const n = alternatives[i] + if (i == 0) { + const rightSeparator = separators[i] + nodes.push( + new Node( + NodeType.alternative, + n, + undefined, + start, + rightSeparator.start + ) + ) + } else if (i == alternatives.length - 1) { + const leftSeparator = separators[i - 1] + nodes.push( + new Node(NodeType.alternative, n, undefined, leftSeparator.end, end) + ) + } else { + const leftSeparator = separators[i - 1] + const rightSeparator = separators[i] + nodes.push( + new Node( + NodeType.alternative, + n, + undefined, + leftSeparator.end, + rightSeparator.start + ) + ) + } } + return nodes } From 83623ff7a615e06545553279b7715961eb1efb53 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Sep 2020 14:28:08 +0200 Subject: [PATCH 149/183] Throw the right exceptions --- cucumber-expressions/javascript/src/Ast.ts | 35 +++++++++++++ .../src/CucumberExpressionParser.ts | 14 +++--- cucumber-expressions/javascript/src/Errors.ts | 50 ++++++++++++++----- 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/cucumber-expressions/javascript/src/Ast.ts b/cucumber-expressions/javascript/src/Ast.ts index 69f989f1ea..76acfc8ef0 100644 --- a/cucumber-expressions/javascript/src/Ast.ts +++ b/cucumber-expressions/javascript/src/Ast.ts @@ -5,6 +5,41 @@ const endParameterCharacter = '}' const beginOptionalCharacter = '(' const endOptionalCharacter = ')' +export function symbolOf(token: TokenType): string { + switch (token) { + case TokenType.beginOptional: + return beginOptionalCharacter + case TokenType.endOptional: + return endOptionalCharacter + case TokenType.beginParameter: + return beginParameterCharacter + case TokenType.endParameter: + return endParameterCharacter + case TokenType.alternation: + return alternationCharacter + } + return '' +} + +export function purposeOf(token: TokenType): string { + switch (token) { + case TokenType.beginOptional: + case TokenType.endOptional: + return 'optional text' + case TokenType.beginParameter: + case TokenType.endParameter: + return 'a parameter' + case TokenType.alternation: + return 'alternation' + } + return '' +} + +export interface Located { + readonly start: number + readonly end: number +} + export class Node { readonly type: NodeType readonly nodes?: ReadonlyArray | undefined diff --git a/cucumber-expressions/javascript/src/CucumberExpressionParser.ts b/cucumber-expressions/javascript/src/CucumberExpressionParser.ts index 22312dc17c..68b681a181 100644 --- a/cucumber-expressions/javascript/src/CucumberExpressionParser.ts +++ b/cucumber-expressions/javascript/src/CucumberExpressionParser.ts @@ -1,5 +1,6 @@ import { Node, NodeType, Token, TokenType } from './Ast' import CucumberExpressionTokenizer from './CucumberExpressionTokenizer' +import { createMissingEndToken } from './Errors' /* * text := token @@ -165,13 +166,12 @@ function parseBetween( // endToken not found if (!lookingAt(tokens, subCurrent, endToken)) { - // throw createMissingEndToken( - // expression, - // beginToken, - // endToken, - // tokens[current] - // ) - throw new Error('TODO: createMissingEndToken') + throw createMissingEndToken( + expression, + beginToken, + endToken, + tokens[current] + ) } // consumes endToken const start = tokens[current].start diff --git a/cucumber-expressions/javascript/src/Errors.ts b/cucumber-expressions/javascript/src/Errors.ts index 3e723e50ab..0e890dbf08 100644 --- a/cucumber-expressions/javascript/src/Errors.ts +++ b/cucumber-expressions/javascript/src/Errors.ts @@ -1,9 +1,10 @@ import ParameterType from './ParameterType' import GeneratedExpression from './GeneratedExpression' +import { Located, purposeOf, symbolOf, Token, TokenType } from './Ast' -class CucumberExpressionError extends Error {} +export class CucumberExpressionError extends Error {} -function createTheEndOfLIneCanNotBeEscaped( +export function createTheEndOfLIneCanNotBeEscaped( expression: string ): CucumberExpressionError { const index = Array.from(expression).length - 1 @@ -18,7 +19,27 @@ function createTheEndOfLIneCanNotBeEscaped( ) } -function createCantEscaped(expression: string, index: number) { +export function createMissingEndToken( + expression: string, + beginToken: TokenType, + endToken: TokenType, + current: Token +) { + const beginSymbol = symbolOf(beginToken) + const endSymbol = symbolOf(endToken) + const purpose = purposeOf(beginToken) + return new CucumberExpressionError( + message( + current.start, + expression, + pointAtLocated(current), + `The '${beginSymbol}' does not have a matching '${endSymbol}'`, + `If you did not intend to use ${purpose} you can use '\\${beginSymbol}' to escape the ${purpose}` + ) + ) +} + +export function createCantEscaped(expression: string, index: number) { return new CucumberExpressionError( message( index, @@ -54,7 +75,18 @@ function pointAt(index: number): string { return pointer.join('') } -class AmbiguousParameterTypeError extends CucumberExpressionError { +function pointAtLocated(node: Located): string { + const pointer = [pointAt(node.start)] + if (node.start + 1 < node.end) { + for (let i = node.start + 1; i < node.end - 1; i++) { + pointer.push('-') + } + pointer.push('^') + } + return pointer.join('') +} + +export class AmbiguousParameterTypeError extends CucumberExpressionError { public static forConstructor( keyName: string, keyValue: string, @@ -100,16 +132,8 @@ I couldn't decide which one to use. You have two options: } } -class UndefinedParameterTypeError extends CucumberExpressionError { +export class UndefinedParameterTypeError extends CucumberExpressionError { constructor(public readonly undefinedParameterTypeName: string) { super(`Undefined parameter type {${undefinedParameterTypeName}}`) } } - -export { - AmbiguousParameterTypeError, - UndefinedParameterTypeError, - CucumberExpressionError, - createTheEndOfLIneCanNotBeEscaped, - createCantEscaped, -} From c16467bcae86e310680407de2a4138daea91e3da Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Sep 2020 23:47:14 +0200 Subject: [PATCH 150/183] Run expression acceptance tests --- .../javascript/test/CucumberExpressionTest.ts | 234 ++++-------------- ...lows-escaped-optional-parameter-types.yaml | 4 + ...llows-parameter-type-in-alternation-1.yaml | 4 + ...llows-parameter-type-in-alternation-2.yaml | 4 + ...low-parameter-adjacent-to-alternation.yaml | 5 + ...lternative-by-adjacent-left-parameter.yaml | 10 + ...mpty-alternative-by-adjacent-optional.yaml | 9 + ...ternative-by-adjacent-right-parameter.yaml | 9 + ...ow-alternation-with-empty-alternative.yaml | 9 + .../does-not-allow-empty-optional.yaml | 9 + ...es-not-allow-optional-parameter-types.yaml | 9 + ...llow-parameter-type-with-left-bracket.yaml | 10 + .../does-not-match-misquoted-string.yaml | 4 + .../expression/doesnt-match-float-as-int.yaml | 5 + ...tches-alternation-in-optional-as-text.yaml | 4 + .../expression/matches-alternation.yaml | 4 + .../matches-anonymous-parameter-type.yaml | 5 + ...empty-string-along-with-other-strings.yaml | 4 + ...e-quoted-empty-string-as-empty-string.yaml | 4 + ...oted-string-with-escaped-double-quote.yaml | 4 + ...uble-quoted-string-with-single-quotes.yaml | 4 + .../matches-double-quoted-string.yaml | 4 + .../matches-doubly-escaped-parenthesis.yaml | 4 + .../matches-doubly-escaped-slash-1.yaml | 4 + .../matches-doubly-escaped-slash-2.yaml | 4 + .../matches-escaped-parenthesis-1.yaml | 4 + .../matches-escaped-parenthesis-2.yaml | 4 + .../matches-escaped-parenthesis-3.yaml | 4 + .../expression/matches-escaped-slash.yaml | 4 + .../testdata/expression/matches-float-1.yaml | 5 + .../testdata/expression/matches-float-2.yaml | 5 + .../testdata/expression/matches-int.yaml | 5 + ...atches-multiple-double-quoted-strings.yaml | 4 + ...atches-multiple-single-quoted-strings.yaml | 4 + ...matches-optional-before-alternation-1.yaml | 4 + ...matches-optional-before-alternation-2.yaml | 4 + ...e-alternation-with-regex-characters-1.yaml | 4 + ...e-alternation-with-regex-characters-2.yaml | 4 + .../matches-optional-in-alternation-1.yaml | 5 + .../matches-optional-in-alternation-2.yaml | 5 + .../matches-optional-in-alternation-3.yaml | 5 + ...empty-string-along-with-other-strings.yaml | 4 + ...e-quoted-empty-string-as-empty-string.yaml | 4 + ...ngle-quoted-string-with-double-quotes.yaml | 4 + ...oted-string-with-escaped-single-quote.yaml | 4 + .../matches-single-quoted-string.yaml | 4 + .../testdata/expression/matches-word.yaml | 4 + .../throws-unknown-parameter-type.yaml | 10 + 48 files changed, 283 insertions(+), 191 deletions(-) create mode 100644 cucumber-expressions/javascript/testdata/expression/allows-escaped-optional-parameter-types.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/allows-parameter-type-in-alternation-1.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/allows-parameter-type-in-alternation-2.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-empty-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-optional-parameter-types.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-match-misquoted-string.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/doesnt-match-float-as-int.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-alternation-in-optional-as-text.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-alternation.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-anonymous-parameter-type.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-parenthesis.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-slash-1.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-slash-2.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-1.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-2.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-3.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-escaped-slash.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-float-1.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-float-2.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-int.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-multiple-double-quoted-strings.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-multiple-single-quoted-strings.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-1.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-2.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-1.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-2.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-3.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/matches-word.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/throws-unknown-parameter-type.yaml diff --git a/cucumber-expressions/javascript/test/CucumberExpressionTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionTest.ts index 0e2c233e77..ba612dd1d5 100644 --- a/cucumber-expressions/javascript/test/CucumberExpressionTest.ts +++ b/cucumber-expressions/javascript/test/CucumberExpressionTest.ts @@ -2,8 +2,51 @@ import assert from 'assert' import CucumberExpression from '../src/CucumberExpression' import ParameterTypeRegistry from '../src/ParameterTypeRegistry' import ParameterType from '../src/ParameterType' +import fs from 'fs' +import yaml from 'js-yaml' +import CucumberExpressionTokenizer from '../src/CucumberExpressionTokenizer' +import { CucumberExpressionError } from '../src/Errors' + +interface Expectation { + expression: string + text: string + expected?: string + exception?: string +} describe('CucumberExpression', () => { + fs.readdirSync('testdata/expression').forEach((testcase) => { + const testCaseData = fs.readFileSync( + `testdata/expression/${testcase}`, + 'utf-8' + ) + const expectation = yaml.safeLoad(testCaseData) as Expectation + it(`${testcase}`, () => { + const parameterTypeRegistry = new ParameterTypeRegistry() + if (expectation.exception == undefined) { + const expression = new CucumberExpression( + expectation.expression, + parameterTypeRegistry + ) + const matches = expression.match(expectation.text) + assert.deepStrictEqual( + JSON.parse( + JSON.stringify(matches.map((value) => value.getValue(null))) + ), // Removes type information. + JSON.parse(expectation.expected) + ) + } else { + assert.throws(() => { + const expression = new CucumberExpression( + expectation.expression, + parameterTypeRegistry + ) + expression.match(expectation.text) + }, new CucumberExpressionError(expectation.exception)) + } + }) + }) + it('documents match arguments', () => { const parameterTypeRegistry = new ParameterTypeRegistry() @@ -15,130 +58,6 @@ describe('CucumberExpression', () => { /// [capture-match-arguments] }) - it('matches word', () => { - assert.deepStrictEqual(match('three {word} mice', 'three blind mice'), [ - 'blind', - ]) - }) - - it('matches double quoted string', () => { - assert.deepStrictEqual(match('three {string} mice', 'three "blind" mice'), [ - 'blind', - ]) - }) - - it('matches multiple double quoted strings', () => { - assert.deepStrictEqual( - match( - 'three {string} and {string} mice', - 'three "blind" and "crippled" mice' - ), - ['blind', 'crippled'] - ) - }) - - it('matches single quoted string', () => { - assert.deepStrictEqual(match('three {string} mice', "three 'blind' mice"), [ - 'blind', - ]) - }) - - it('matches multiple single quoted strings', () => { - assert.deepStrictEqual( - match( - 'three {string} and {string} mice', - "three 'blind' and 'crippled' mice" - ), - ['blind', 'crippled'] - ) - }) - - it('does not match misquoted string', () => { - assert.deepStrictEqual( - match('three {string} mice', 'three "blind\' mice'), - null - ) - }) - - it('matches single quoted string with double quotes', () => { - assert.deepStrictEqual( - match('three {string} mice', 'three \'"blind"\' mice'), - ['"blind"'] - ) - }) - - it('matches double quoted string with single quotes', () => { - assert.deepStrictEqual( - match('three {string} mice', 'three "\'blind\'" mice'), - ["'blind'"] - ) - }) - - it('matches double quoted string with escaped double quote', () => { - assert.deepStrictEqual( - match('three {string} mice', 'three "bl\\"nd" mice'), - ['bl"nd'] - ) - }) - - it('matches single quoted string with escaped single quote', () => { - assert.deepStrictEqual( - match('three {string} mice', "three 'bl\\'nd' mice"), - ["bl'nd"] - ) - }) - - it('matches single quoted string with escaped single quote', () => { - assert.deepStrictEqual( - match('three {string} mice', "three 'bl\\'nd' mice"), - ["bl'nd"] - ) - }) - - it('matches single quoted empty string as empty string', () => { - assert.deepStrictEqual(match('three {string} mice', "three '' mice"), ['']) - }) - - it('matches double quoted empty string as empty string ', () => { - assert.deepStrictEqual(match('three {string} mice', 'three "" mice'), ['']) - }) - - it('matches single quoted empty string as empty string, along with other strings', () => { - assert.deepStrictEqual( - match('three {string} and {string} mice', "three '' and 'handsome' mice"), - ['', 'handsome'] - ) - }) - - it('matches double quoted empty string as empty string, along with other strings', () => { - assert.deepStrictEqual( - match('three {string} and {string} mice', 'three "" and "handsome" mice'), - ['', 'handsome'] - ) - }) - - it('matches escaped parenthesis', () => { - assert.deepStrictEqual( - match( - 'three \\(exceptionally) {string} mice', - 'three (exceptionally) "blind" mice' - ), - ['blind'] - ) - }) - - it('matches escaped slash', () => { - assert.deepStrictEqual(match('12\\/2020', '12/2020'), []) - }) - - it('matches int', () => { - assert.deepStrictEqual(match('{int}', '22'), [22]) - }) - - it("doesn't match float as int", () => { - assert.deepStrictEqual(match('{int}', '1.22'), null) - }) - it('matches float', () => { assert.deepStrictEqual(match('{float}', ''), null) assert.deepStrictEqual(match('{float}', '.'), null) @@ -178,73 +97,6 @@ describe('CucumberExpression', () => { assert.deepEqual(match('{float}', '0'), [0]) }) - it('matches anonymous', () => { - assert.deepStrictEqual(match('{}', '0.22'), ['0.22']) - }) - - it('throws unknown parameter type', () => { - try { - match('{unknown}', 'something') - assert.fail() - } catch (expected) { - assert.strictEqual(expected.message, 'Undefined parameter type {unknown}') - } - }) - - it('does not allow optional parameter types', () => { - try { - match('({int})', '3') - assert.fail() - } catch (expected) { - assert.strictEqual( - expected.message, - 'Parameter types cannot be optional: ({int})' - ) - } - }) - - it('allows escaped optional parameter types', () => { - assert.deepStrictEqual(match('\\({int})', '(3)'), [3]) - }) - - it('does not allow text/parameter type alternation', () => { - try { - match('x/{int}', '3') - assert.fail() - } catch (expected) { - assert.strictEqual( - expected.message, - 'Parameter types cannot be alternative: x/{int}' - ) - } - }) - - it('does not allow parameter type/text alternation', () => { - try { - match('{int}/x', '3') - assert.fail() - } catch (expected) { - assert.strictEqual( - expected.message, - 'Parameter types cannot be alternative: {int}/x' - ) - } - }) - - for (const c of '[]()$.|?*+'.split('')) { - it(`does not allow parameter type with ${c}`, () => { - try { - match(`{${c}string}`, 'something') - assert.fail() - } catch (expected) { - assert.strictEqual( - expected.message, - `Illegal character '${c}' in parameter name {${c}string}` - ) - } - }) - } - it('exposes source', () => { const expr = 'I have {int} cuke(s)' assert.strictEqual( diff --git a/cucumber-expressions/javascript/testdata/expression/allows-escaped-optional-parameter-types.yaml b/cucumber-expressions/javascript/testdata/expression/allows-escaped-optional-parameter-types.yaml new file mode 100644 index 0000000000..a00b45acef --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/allows-escaped-optional-parameter-types.yaml @@ -0,0 +1,4 @@ +expression: \({int}) +text: (3) +expected: |- + [3] diff --git a/cucumber-expressions/javascript/testdata/expression/allows-parameter-type-in-alternation-1.yaml b/cucumber-expressions/javascript/testdata/expression/allows-parameter-type-in-alternation-1.yaml new file mode 100644 index 0000000000..bb1a6f21b1 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/allows-parameter-type-in-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: i18n +expected: |- + [18] diff --git a/cucumber-expressions/javascript/testdata/expression/allows-parameter-type-in-alternation-2.yaml b/cucumber-expressions/javascript/testdata/expression/allows-parameter-type-in-alternation-2.yaml new file mode 100644 index 0000000000..cdddce7d84 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/allows-parameter-type-in-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: a11y +expected: |- + [11] diff --git a/cucumber-expressions/javascript/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml b/cucumber-expressions/javascript/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml new file mode 100644 index 0000000000..9e2ecdfbe1 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml @@ -0,0 +1,5 @@ +expression: |- + {int}st/nd/rd/th +text: 3rd +expected: |- + [3] diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml new file mode 100644 index 0000000000..b32540a4a9 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml @@ -0,0 +1,10 @@ +expression: |- + {int}/x +text: '3' +exception: |- + This Cucumber Expression has a problem at column 6: + + {int}/x + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml new file mode 100644 index 0000000000..a0aab0e5a9 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml @@ -0,0 +1,9 @@ +expression: three (brown)/black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (brown)/black mice + ^-----^ + An alternative may not exclusively contain optionals. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml new file mode 100644 index 0000000000..50250f00aa --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml @@ -0,0 +1,9 @@ +expression: x/{int} +text: '3' +exception: |- + This Cucumber Expression has a problem at column 3: + + x/{int} + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml new file mode 100644 index 0000000000..b724cfa77f --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml @@ -0,0 +1,9 @@ +expression: three brown//black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three brown//black mice + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-empty-optional.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-empty-optional.yaml new file mode 100644 index 0000000000..00e341af0b --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-empty-optional.yaml @@ -0,0 +1,9 @@ +expression: three () mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three () mice + ^^ + An optional must contain some text. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-optional-parameter-types.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-optional-parameter-types.yaml new file mode 100644 index 0000000000..b88061e9b4 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-optional-parameter-types.yaml @@ -0,0 +1,9 @@ +expression: ({int}) +text: '3' +exception: |- + This Cucumber Expression has a problem at column 2: + + ({int}) + ^---^ + An optional may not contain a parameter type. + If you did not mean to use an parameter type you can use '\{' to escape the the '{' diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml new file mode 100644 index 0000000000..1dd65aa276 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml @@ -0,0 +1,10 @@ +expression: |- + {[string]} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {[string]} + ^--------^ + Parameter names may not contain '[]()$.|?*+'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-match-misquoted-string.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-match-misquoted-string.yaml new file mode 100644 index 0000000000..18023180af --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-match-misquoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind' mice +expected: |- + null diff --git a/cucumber-expressions/javascript/testdata/expression/doesnt-match-float-as-int.yaml b/cucumber-expressions/javascript/testdata/expression/doesnt-match-float-as-int.yaml new file mode 100644 index 0000000000..d66b586430 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/doesnt-match-float-as-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '1.22' +expected: |- + null diff --git a/cucumber-expressions/javascript/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/javascript/testdata/expression/matches-alternation-in-optional-as-text.yaml new file mode 100644 index 0000000000..15fe78bf53 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-alternation-in-optional-as-text.yaml @@ -0,0 +1,4 @@ +expression: three( brown/black) mice +text: three brown/black mice +expected: |- + [] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-alternation.yaml b/cucumber-expressions/javascript/testdata/expression/matches-alternation.yaml new file mode 100644 index 0000000000..20a9b9a728 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-alternation.yaml @@ -0,0 +1,4 @@ +expression: mice/rats and rats\/mice +text: rats and rats/mice +expected: |- + [] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-anonymous-parameter-type.yaml b/cucumber-expressions/javascript/testdata/expression/matches-anonymous-parameter-type.yaml new file mode 100644 index 0000000000..fc954960df --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-anonymous-parameter-type.yaml @@ -0,0 +1,5 @@ +expression: |- + {} +text: '0.22' +expected: |- + ["0.22"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c3e1962e22 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "" and "handsome" mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..89315b62ef --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "" mice +expected: |- + [""] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml new file mode 100644 index 0000000000..fe30a044c1 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "bl\"nd" mice +expected: |- + ["bl\"nd"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml new file mode 100644 index 0000000000..25fcf304c1 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "'blind'" mice +expected: |- + ["'blind'"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string.yaml b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string.yaml new file mode 100644 index 0000000000..8b0560f332 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-double-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-parenthesis.yaml b/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-parenthesis.yaml new file mode 100644 index 0000000000..902a084103 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-parenthesis.yaml @@ -0,0 +1,4 @@ +expression: three \\(exceptionally) \\{string} mice +text: three \exceptionally \"blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-slash-1.yaml b/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-slash-1.yaml new file mode 100644 index 0000000000..94e531eca7 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-slash-1.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: 12\ +expected: |- + [] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-slash-2.yaml b/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-slash-2.yaml new file mode 100644 index 0000000000..9c9c735b86 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-doubly-escaped-slash-2.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: '2020' +expected: |- + [] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-1.yaml b/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-1.yaml new file mode 100644 index 0000000000..171df89ee1 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-1.yaml @@ -0,0 +1,4 @@ +expression: three \(exceptionally) \{string} mice +text: three (exceptionally) {string} mice +expected: |- + [] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-2.yaml b/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-2.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-2.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-3.yaml b/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-3.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-escaped-parenthesis-3.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-escaped-slash.yaml b/cucumber-expressions/javascript/testdata/expression/matches-escaped-slash.yaml new file mode 100644 index 0000000000..d8b3933399 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-escaped-slash.yaml @@ -0,0 +1,4 @@ +expression: 12\/2020 +text: 12/2020 +expected: |- + [] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-float-1.yaml b/cucumber-expressions/javascript/testdata/expression/matches-float-1.yaml new file mode 100644 index 0000000000..fe7e8b1869 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-float-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '0.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-float-2.yaml b/cucumber-expressions/javascript/testdata/expression/matches-float-2.yaml new file mode 100644 index 0000000000..c1e5894eac --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-float-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-int.yaml b/cucumber-expressions/javascript/testdata/expression/matches-int.yaml new file mode 100644 index 0000000000..bcd3763886 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '22' +expected: |- + [22] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-multiple-double-quoted-strings.yaml b/cucumber-expressions/javascript/testdata/expression/matches-multiple-double-quoted-strings.yaml new file mode 100644 index 0000000000..6c74bc2350 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-multiple-double-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "blind" and "crippled" mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-multiple-single-quoted-strings.yaml b/cucumber-expressions/javascript/testdata/expression/matches-multiple-single-quoted-strings.yaml new file mode 100644 index 0000000000..5037821c14 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-multiple-single-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three 'blind' and 'crippled' mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-1.yaml b/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-1.yaml new file mode 100644 index 0000000000..821776715b --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three brown mice +expected: |- + [] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-2.yaml b/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-2.yaml new file mode 100644 index 0000000000..71b3a341f1 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three rats +expected: |- + [] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml b/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml new file mode 100644 index 0000000000..2632f410ce --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 2 seconds? +expected: |- + [2] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml b/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml new file mode 100644 index 0000000000..7b30f667bc --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 1 second. +expected: |- + [1] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-1.yaml b/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-1.yaml new file mode 100644 index 0000000000..6574bb4bdf --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 3 rats +expected: |- + [3] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-2.yaml b/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-2.yaml new file mode 100644 index 0000000000..4eb0f0e1c6 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 2 mice +expected: |- + [2] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-3.yaml b/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-3.yaml new file mode 100644 index 0000000000..964fa6489e --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-optional-in-alternation-3.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 1 mouse +expected: |- + [1] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c963dcf1c7 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three '' and 'handsome' mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..cff2a2d1ec --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '' mice +expected: |- + [""] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml new file mode 100644 index 0000000000..eb9ed537cd --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '"blind"' mice +expected: |- + ["\"blind\""] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml new file mode 100644 index 0000000000..4c2b0055b9 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'bl\'nd' mice +expected: |- + ["bl'nd"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string.yaml b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string.yaml new file mode 100644 index 0000000000..6c8f4652a5 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-single-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'blind' mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/javascript/testdata/expression/matches-word.yaml b/cucumber-expressions/javascript/testdata/expression/matches-word.yaml new file mode 100644 index 0000000000..358fd3afd1 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/matches-word.yaml @@ -0,0 +1,4 @@ +expression: three {word} mice +text: three blind mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/javascript/testdata/expression/throws-unknown-parameter-type.yaml b/cucumber-expressions/javascript/testdata/expression/throws-unknown-parameter-type.yaml new file mode 100644 index 0000000000..384e3a48c3 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/throws-unknown-parameter-type.yaml @@ -0,0 +1,10 @@ +expression: |- + {unknown} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {unknown} + ^-------^ + Undefined parameter type 'unknown'. + Please register a ParameterType for 'unknown' From d580f5fced600c849d82a8b00ae3946f72760c07 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 25 Sep 2020 00:54:02 +0200 Subject: [PATCH 151/183] More tests pass --- cucumber-expressions/javascript/src/Ast.ts | 7 + .../javascript/src/CucumberExpression.ts | 195 ++++++++++-------- cucumber-expressions/javascript/src/Errors.ts | 87 +++++++- .../javascript/src/ParameterType.ts | 18 +- 4 files changed, 213 insertions(+), 94 deletions(-) diff --git a/cucumber-expressions/javascript/src/Ast.ts b/cucumber-expressions/javascript/src/Ast.ts index 76acfc8ef0..07d4b515b1 100644 --- a/cucumber-expressions/javascript/src/Ast.ts +++ b/cucumber-expressions/javascript/src/Ast.ts @@ -66,6 +66,13 @@ export class Node { this.start = start this.end = end } + + text(): string { + if (this.nodes) { + return this.nodes.map((value) => value.text()).join('') + } + return this.token + } } export enum NodeType { diff --git a/cucumber-expressions/javascript/src/CucumberExpression.ts b/cucumber-expressions/javascript/src/CucumberExpression.ts index 19d6ed0a3e..7ef8fafec1 100644 --- a/cucumber-expressions/javascript/src/CucumberExpression.ts +++ b/cucumber-expressions/javascript/src/CucumberExpression.ts @@ -2,24 +2,20 @@ import ParameterTypeRegistry from './ParameterTypeRegistry' import ParameterType from './ParameterType' import TreeRegexp from './TreeRegexp' import Argument from './Argument' -import { CucumberExpressionError, UndefinedParameterTypeError } from './Errors' +import { + CucumberExpressionError, + createOptionalMayNotBeEmpty, + createParameterIsNotAllowedInOptional, + createAlternativeMayNotBeEmpty, + createAlternativeMayNotExclusivelyContainOptionals, + createInvalidParameterTypeName, + createUndefinedParameterType, +} from './Errors' import Expression from './Expression' +import CucumberExpressionParser from './CucumberExpressionParser' +import { Node, NodeType } from './Ast' -// RegExps with the g flag are stateful in JavaScript. In order to be able -// to reuse them we have to wrap them in a function. -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test - -// Does not include (){} characters because they have special meaning -const ESCAPE_REGEXP = () => /([\\^[$.|?*+])/g -const PARAMETER_REGEXP = () => /(\\\\)?{([^}]*)}/g -const OPTIONAL_REGEXP = () => /(\\\\)?\(([^)]+)\)/g -const ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = () => - /([^\s^/]+)((\/[^\s^/]+)+)/g -const DOUBLE_ESCAPE = '\\\\' -const PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = - 'Parameter types cannot be alternative: ' -const PARAMETER_TYPES_CANNOT_BE_OPTIONAL = - 'Parameter types cannot be optional: ' +const ESCAPE_PATTERN = () => /([\\^[({$.|?*+})\]])/g export default class CucumberExpression implements Expression { private readonly parameterTypes: Array> = [] @@ -33,69 +29,118 @@ export default class CucumberExpression implements Expression { private readonly expression: string, private readonly parameterTypeRegistry: ParameterTypeRegistry ) { - let expr = this.processEscapes(expression) - expr = this.processOptional(expr) - expr = this.processAlternation(expr) - expr = this.processParameters(expr, parameterTypeRegistry) - expr = `^${expr}$` + const parser = new CucumberExpressionParser() + const ast = parser.parse(expression) + const pattern = this.rewriteToRegex(ast) + this.treeRegexp = new TreeRegexp(pattern) + } - this.treeRegexp = new TreeRegexp(expr) + private rewriteToRegex(node: Node): string { + switch (node.type) { + case NodeType.text: + return CucumberExpression.escapeRegex(node.text()) + case NodeType.optional: + return this.rewriteOptional(node) + case NodeType.alternation: + return this.rewriteAlternation(node) + case NodeType.alternative: + return this.rewriteAlternative(node) + case NodeType.parameter: + return this.rewriteParameter(node) + case NodeType.expression: + return this.rewriteExpression(node) + default: + throw new Error(node.type) + } } - private processEscapes(expression: string) { - return expression.replace(ESCAPE_REGEXP(), '\\$1') + private static escapeRegex(expression: string) { + return expression.replace(ESCAPE_PATTERN(), '\\$1') } - private processOptional(expression: string) { - return expression.replace(OPTIONAL_REGEXP(), (match, p1, p2) => { - if (p1 === DOUBLE_ESCAPE) { - return `\\(${p2}\\)` + private rewriteOptional(node: Node): string { + this.assertNoParameters(node, (astNode) => + createParameterIsNotAllowedInOptional(astNode, this.expression) + ) + this.assertNotEmpty(node, (astNode) => + createOptionalMayNotBeEmpty(astNode, this.expression) + ) + const regex = node.nodes.map((node) => this.rewriteToRegex(node)).join('') + return `(?:${regex})?` + } + + private rewriteAlternation(node: Node) { + // Make sure the alternative parts aren't empty and don't contain parameter types + node.nodes.forEach((alternative) => { + if (alternative.nodes.length == 0) { + throw createAlternativeMayNotBeEmpty(alternative, this.expression) } - this.checkNoParameterType(p2, PARAMETER_TYPES_CANNOT_BE_OPTIONAL) - return `(?:${p2})?` + this.assertNotEmpty(alternative, (astNode) => + createAlternativeMayNotExclusivelyContainOptionals( + astNode, + this.expression + ) + ) }) + const regex = node.nodes.map((node) => this.rewriteToRegex(node)).join('|') + return `(?:${regex})` } - private processAlternation(expression: string) { - return expression.replace( - ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP(), - (match) => { - // replace \/ with / - // replace / with | - const replacement = match.replace(/\//g, '|').replace(/\\\|/g, '/') - if (replacement.indexOf('|') !== -1) { - for (const part of replacement.split(/\|/)) { - this.checkNoParameterType( - part, - PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE - ) - } - return `(?:${replacement})` - } else { - return replacement - } - } - ) + private rewriteAlternative(node: Node) { + return node.nodes.map((lastNode) => this.rewriteToRegex(lastNode)).join('') } - private processParameters( - expression: string, - parameterTypeRegistry: ParameterTypeRegistry + private rewriteParameter(node: Node) { + const name = node.text() + if (!ParameterType.isValidParameterTypeName(name)) { + throw createInvalidParameterTypeName(name) + } + + const parameterType = this.parameterTypeRegistry.lookupByTypeName(name) + if (!parameterType) { + throw createUndefinedParameterType(node, this.expression, name) + } + this.parameterTypes.push(parameterType) + const regexps = parameterType.regexpStrings + if (regexps.length == 1) { + return `(${regexps[0]})` + } + const regex = node.nodes + .map((node) => this.rewriteToRegex(node)) + .join(')|(?:') + return `((?::${regex}))` + } + + private rewriteExpression(node: Node) { + const regex = node.nodes.map((node) => this.rewriteToRegex(node)).join('') + return `^${regex}$` + } + + private assertNotEmpty( + node: Node, + createNodeWasNotEmptyException: (astNode: Node) => CucumberExpressionError ) { - return expression.replace(PARAMETER_REGEXP(), (match, p1, p2) => { - if (p1 === DOUBLE_ESCAPE) { - return `\\{${p2}\\}` - } + const textNodes = node.nodes.filter( + (astNode) => NodeType.text == astNode.type + ) - const typeName = p2 - ParameterType.checkParameterTypeName(typeName) - const parameterType = parameterTypeRegistry.lookupByTypeName(typeName) - if (!parameterType) { - throw new UndefinedParameterTypeError(typeName) - } - this.parameterTypes.push(parameterType) - return buildCaptureRegexp(parameterType.regexpStrings) - }) + if (textNodes.length == 0) { + throw createNodeWasNotEmptyException(node) + } + } + + private assertNoParameters( + node: Node, + createNodeContainedAParameterError: ( + astNode: Node + ) => CucumberExpressionError + ) { + const parameterNodes = node.nodes.filter( + (astNode) => NodeType.parameter == astNode.type + ) + if (parameterNodes.length > 0) { + throw createNodeContainedAParameterError(node) + } } public match(text: string): ReadonlyArray> { @@ -109,22 +154,4 @@ export default class CucumberExpression implements Expression { get source(): string { return this.expression } - - private checkNoParameterType(s: string, message: string) { - if (s.match(PARAMETER_REGEXP())) { - throw new CucumberExpressionError(message + this.source) - } - } -} - -function buildCaptureRegexp(regexps: ReadonlyArray) { - if (regexps.length === 1) { - return `(${regexps[0]})` - } - - const captureGroups = regexps.map((group) => { - return `(?:${group})` - }) - - return `(${captureGroups.join('|')})` } diff --git a/cucumber-expressions/javascript/src/Errors.ts b/cucumber-expressions/javascript/src/Errors.ts index 0e890dbf08..0c5d346977 100644 --- a/cucumber-expressions/javascript/src/Errors.ts +++ b/cucumber-expressions/javascript/src/Errors.ts @@ -1,9 +1,66 @@ import ParameterType from './ParameterType' import GeneratedExpression from './GeneratedExpression' -import { Located, purposeOf, symbolOf, Token, TokenType } from './Ast' +import { Located, Node, purposeOf, symbolOf, Token, TokenType } from './Ast' export class CucumberExpressionError extends Error {} +export function createAlternativeMayNotExclusivelyContainOptionals( + node: Node, + expression: string +): CucumberExpressionError { + return new CucumberExpressionError( + message( + node.start, + expression, + pointAtLocated(node), + 'An alternative may not exclusively contain optionals', + "If you did not mean to use an optional you can use '\\(' to escape the the '('" + ) + ) +} +export function createAlternativeMayNotBeEmpty( + node: Node, + expression: string +): CucumberExpressionError { + return new CucumberExpressionError( + message( + node.start, + expression, + pointAtLocated(node), + 'Alternative may not be empty', + "If you did not mean to use an alternative you can use '\\/' to escape the the '/'" + ) + ) +} +export function createOptionalMayNotBeEmpty( + node: Node, + expression: string +): CucumberExpressionError { + return new CucumberExpressionError( + message( + node.start, + expression, + pointAtLocated(node), + 'An optional must contain some text', + "If you did not mean to use an optional you can use '\\(' to escape the the '('" + ) + ) +} +export function createParameterIsNotAllowedInOptional( + node: Node, + expression: string +): CucumberExpressionError { + return new CucumberExpressionError( + message( + node.start, + expression, + pointAtLocated(node), + 'An optional may not contain a parameter type', + "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'" + ) + ) +} + export function createTheEndOfLIneCanNotBeEscaped( expression: string ): CucumberExpressionError { @@ -51,6 +108,14 @@ export function createCantEscaped(expression: string, index: number) { ) } +export function createInvalidParameterTypeName(name: string) { + return new CucumberExpressionError( + 'Illegal character in parameter name {' + + name + + "}. Parameter names may not contain '[]()$.|?*+'" + ) +} + function message( index: number, expression: string, @@ -133,7 +198,23 @@ I couldn't decide which one to use. You have two options: } export class UndefinedParameterTypeError extends CucumberExpressionError { - constructor(public readonly undefinedParameterTypeName: string) { - super(`Undefined parameter type {${undefinedParameterTypeName}}`) + constructor(public readonly message: string) { + super(message) } } + +export function createUndefinedParameterType( + node: Node, + expression: string, + undefinedParameterTypeName: string +) { + return new UndefinedParameterTypeError( + message( + node.start, + expression, + pointAtLocated(node), + `Undefined parameter type '${undefinedParameterTypeName}'`, + `Please register a ParameterType for '${undefinedParameterTypeName}'` + ) + ) +} diff --git a/cucumber-expressions/javascript/src/ParameterType.ts b/cucumber-expressions/javascript/src/ParameterType.ts index 40c8b51418..2e36a240c9 100644 --- a/cucumber-expressions/javascript/src/ParameterType.ts +++ b/cucumber-expressions/javascript/src/ParameterType.ts @@ -1,4 +1,7 @@ -import { CucumberExpressionError } from './Errors' +import { + createInvalidParameterTypeName, + CucumberExpressionError, +} from './Errors' const ILLEGAL_PARAMETER_NAME_PATTERN = /([[\]()$.|?*+])/ const UNESCAPE_PATTERN = () => /(\\([[$.|?*+\]]))/g @@ -17,15 +20,16 @@ export default class ParameterType { } public static checkParameterTypeName(typeName: string) { - const unescapedTypeName = typeName.replace(UNESCAPE_PATTERN(), '$2') - const match = unescapedTypeName.match(ILLEGAL_PARAMETER_NAME_PATTERN) - if (match) { - throw new CucumberExpressionError( - `Illegal character '${match[1]}' in parameter name {${unescapedTypeName}}` - ) + if (!this.isValidParameterTypeName(typeName)) { + throw createInvalidParameterTypeName(typeName) } } + public static isValidParameterTypeName(typeName: string) { + const unescapedTypeName = typeName.replace(UNESCAPE_PATTERN(), '$2') + return !unescapedTypeName.match(ILLEGAL_PARAMETER_NAME_PATTERN) + } + public regexpStrings: ReadonlyArray /** From d82b0e62dc30f2227fe5d0bd0539cb4f7d6885c9 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 25 Sep 2020 01:13:52 +0200 Subject: [PATCH 152/183] All tests pass! --- cucumber-expressions/javascript/examples.txt | 14 +++++++++++++- .../javascript/src/CucumberExpression.ts | 11 ++++------- .../javascript/test/CucumberExpressionTest.ts | 16 +++++++++++++--- .../javascript/test/CustomParameterTypeTest.ts | 5 ++++- .../javascript/test/ExpressionExamplesTest.ts | 2 +- .../javascript/test/ExpressionFactoryTest.ts | 6 ------ 6 files changed, 35 insertions(+), 19 deletions(-) diff --git a/cucumber-expressions/javascript/examples.txt b/cucumber-expressions/javascript/examples.txt index 12bee4dc01..3a8288d34d 100644 --- a/cucumber-expressions/javascript/examples.txt +++ b/cucumber-expressions/javascript/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \[]^$.|?*+ +I have {int} cuke(s) and some \\[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- @@ -29,3 +29,15 @@ I have 22 cukes in my belly now I have {} cuke(s) in my {} now I have 22 cukes in my belly now ["22","belly"] +--- +/^a (pre-commercial transaction |pre buyer fee model )?purchase(?: for \$(\d+))?$/ +a purchase for $33 +[null,33] +--- +Some ${float} of cukes at {int}° Celsius +Some $3.50 of cukes at 42° Celsius +[3.5,42] +--- +I select the {int}st/nd/rd/th Cucumber +I select the 3rd Cucumber +[3] diff --git a/cucumber-expressions/javascript/src/CucumberExpression.ts b/cucumber-expressions/javascript/src/CucumberExpression.ts index 7ef8fafec1..1c59c10ac3 100644 --- a/cucumber-expressions/javascript/src/CucumberExpression.ts +++ b/cucumber-expressions/javascript/src/CucumberExpression.ts @@ -3,13 +3,13 @@ import ParameterType from './ParameterType' import TreeRegexp from './TreeRegexp' import Argument from './Argument' import { - CucumberExpressionError, - createOptionalMayNotBeEmpty, - createParameterIsNotAllowedInOptional, createAlternativeMayNotBeEmpty, createAlternativeMayNotExclusivelyContainOptionals, createInvalidParameterTypeName, + createOptionalMayNotBeEmpty, + createParameterIsNotAllowedInOptional, createUndefinedParameterType, + CucumberExpressionError, } from './Errors' import Expression from './Expression' import CucumberExpressionParser from './CucumberExpressionParser' @@ -105,10 +105,7 @@ export default class CucumberExpression implements Expression { if (regexps.length == 1) { return `(${regexps[0]})` } - const regex = node.nodes - .map((node) => this.rewriteToRegex(node)) - .join(')|(?:') - return `((?::${regex}))` + return `((?:${regexps.join(')|(?:')}))` } private rewriteExpression(node: Node) { diff --git a/cucumber-expressions/javascript/test/CucumberExpressionTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionTest.ts index ba612dd1d5..ee57119707 100644 --- a/cucumber-expressions/javascript/test/CucumberExpressionTest.ts +++ b/cucumber-expressions/javascript/test/CucumberExpressionTest.ts @@ -4,7 +4,6 @@ import ParameterTypeRegistry from '../src/ParameterTypeRegistry' import ParameterType from '../src/ParameterType' import fs from 'fs' import yaml from 'js-yaml' -import CucumberExpressionTokenizer from '../src/CucumberExpressionTokenizer' import { CucumberExpressionError } from '../src/Errors' interface Expectation { @@ -17,7 +16,7 @@ interface Expectation { describe('CucumberExpression', () => { fs.readdirSync('testdata/expression').forEach((testcase) => { const testCaseData = fs.readFileSync( - `testdata/expression/${testcase}`, + `testdata/expression/matches-int.yaml`, 'utf-8' ) const expectation = yaml.safeLoad(testCaseData) as Expectation @@ -168,7 +167,7 @@ describe('CucumberExpression', () => { }) describe('escapes special characters', () => { - const special = ['\\', '[', ']', '^', '$', '.', '|', '?', '*', '+'] + const special = ['[', ']', '^', '$', '.', '|', '?', '*', '+'] special.forEach((character) => { it(`escapes ${character}`, () => { const expr = `I have {int} cuke(s) and ${character}` @@ -192,6 +191,17 @@ describe('CucumberExpression', () => { assert.strictEqual(arg1.getValue(null), 800) }) + it(`escapes \\`, () => { + const expr = `I have {int} cuke(s) and \\\\` + const expression = new CucumberExpression( + expr, + new ParameterTypeRegistry() + ) + assert.strictEqual(expression.match(`I have 800 cukes and 3`), null) + const arg1 = expression.match(`I have 800 cukes and \\`)[0] + assert.strictEqual(arg1.getValue(null), 800) + }) + it(`escapes |`, () => { const expr = `I have {int} cuke(s) and a|b` const expression = new CucumberExpression( diff --git a/cucumber-expressions/javascript/test/CustomParameterTypeTest.ts b/cucumber-expressions/javascript/test/CustomParameterTypeTest.ts index 0eca6398c5..620da82bab 100644 --- a/cucumber-expressions/javascript/test/CustomParameterTypeTest.ts +++ b/cucumber-expressions/javascript/test/CustomParameterTypeTest.ts @@ -42,7 +42,10 @@ describe('Custom parameter type', () => { assert.throws( () => new ParameterType('[string]', /.*/, String, (s) => s, false, true), - { message: "Illegal character '[' in parameter name {[string]}" } + { + message: + "Illegal character in parameter name {[string]}. Parameter names may not contain '[]()$.|?*+'", + } ) }) diff --git a/cucumber-expressions/javascript/test/ExpressionExamplesTest.ts b/cucumber-expressions/javascript/test/ExpressionExamplesTest.ts index e9f09ce649..65d221e944 100644 --- a/cucumber-expressions/javascript/test/ExpressionExamplesTest.ts +++ b/cucumber-expressions/javascript/test/ExpressionExamplesTest.ts @@ -6,7 +6,7 @@ import ParameterTypeRegistry from '../src/ParameterTypeRegistry' describe('examples.txt', () => { const match = (expressionText: string, text: string) => { - const m = /\/(.*)\//.exec(expressionText) + const m = /^\/(.*)\/$/.exec(expressionText) const expression = m ? new RegularExpression(new RegExp(m[1]), new ParameterTypeRegistry()) : new CucumberExpression(expressionText, new ParameterTypeRegistry()) diff --git a/cucumber-expressions/javascript/test/ExpressionFactoryTest.ts b/cucumber-expressions/javascript/test/ExpressionFactoryTest.ts index 6f99ea8120..9520e4d1d2 100644 --- a/cucumber-expressions/javascript/test/ExpressionFactoryTest.ts +++ b/cucumber-expressions/javascript/test/ExpressionFactoryTest.ts @@ -23,10 +23,4 @@ describe('ExpressionFactory', () => { CucumberExpression ) }) - - it('creates an UndefinedParameterTypeExpression', () => { - assert.throws(() => expressionFactory.createExpression('{x}'), { - message: 'Undefined parameter type {x}', - }) - }) }) From e8e9cb4634cd50b855c0c4b7f5fafe2745328e72 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 25 Sep 2020 01:19:10 +0200 Subject: [PATCH 153/183] Fix lint --- cucumber-expressions/javascript/test/CucumberExpressionTest.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber-expressions/javascript/test/CucumberExpressionTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionTest.ts index ee57119707..937185e65e 100644 --- a/cucumber-expressions/javascript/test/CucumberExpressionTest.ts +++ b/cucumber-expressions/javascript/test/CucumberExpressionTest.ts @@ -3,7 +3,8 @@ import CucumberExpression from '../src/CucumberExpression' import ParameterTypeRegistry from '../src/ParameterTypeRegistry' import ParameterType from '../src/ParameterType' import fs from 'fs' -import yaml from 'js-yaml' +// eslint-disable-next-line node/no-extraneous-import +import yaml from 'js-yaml' // why? import { CucumberExpressionError } from '../src/Errors' interface Expectation { From d1f0380559cfd68d72d56fddb5d7035ed2ecbae4 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 25 Sep 2020 01:31:34 +0200 Subject: [PATCH 154/183] Keep api the same --- cucumber-expressions/javascript/src/Errors.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cucumber-expressions/javascript/src/Errors.ts b/cucumber-expressions/javascript/src/Errors.ts index 0c5d346977..75cdd3aa49 100644 --- a/cucumber-expressions/javascript/src/Errors.ts +++ b/cucumber-expressions/javascript/src/Errors.ts @@ -198,7 +198,10 @@ I couldn't decide which one to use. You have two options: } export class UndefinedParameterTypeError extends CucumberExpressionError { - constructor(public readonly message: string) { + constructor( + public readonly undefinedParameterTypeName: string, + message: string + ) { super(message) } } @@ -209,6 +212,7 @@ export function createUndefinedParameterType( undefinedParameterTypeName: string ) { return new UndefinedParameterTypeError( + undefinedParameterTypeName, message( node.start, expression, From ce04c3262575b4dfe8fad735b9e9061d5738a34a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 1 Oct 2020 11:50:18 +0100 Subject: [PATCH 155/183] Formatting --- cucumber-expressions/go/cucumber_expression_tokenizer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cucumber-expressions/go/cucumber_expression_tokenizer.go b/cucumber-expressions/go/cucumber_expression_tokenizer.go index 34f6f40d79..c99dd838b4 100644 --- a/cucumber-expressions/go/cucumber_expression_tokenizer.go +++ b/cucumber-expressions/go/cucumber_expression_tokenizer.go @@ -82,4 +82,3 @@ func tokenize(expression string) ([]token, error) { tokens = append(tokens, token) return tokens, nil } - From 2f7bd3617dc0dc2c12bfd8680a48bd8ec838711b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 1 Oct 2020 12:28:50 +0100 Subject: [PATCH 156/183] First passing ruby test --- cucumber-expressions/ruby/examples.txt | 14 +++++++++- .../cucumber_expression_tokenizer.rb | 12 +++++++++ .../cucumber_expression_tokenizer_spec.rb | 25 +++++++++++++++++ .../ast/alternation-followed-by-optional.yaml | 17 ++++++++++++ .../ruby/testdata/ast/alternation-phrase.yaml | 16 +++++++++++ .../ast/alternation-with-parameter.yaml | 27 +++++++++++++++++++ .../alternation-with-unused-end-optional.yaml | 15 +++++++++++ ...lternation-with-unused-start-optional.yaml | 8 ++++++ .../ast/alternation-with-white-space.yaml | 12 +++++++++ .../ruby/testdata/ast/alternation.yaml | 12 +++++++++ .../testdata/ast/anonymous-parameter.yaml | 5 ++++ .../ruby/testdata/ast/closing-brace.yaml | 5 ++++ .../testdata/ast/closing-parenthesis.yaml | 5 ++++ .../ruby/testdata/ast/empty-alternation.yaml | 8 ++++++ .../ruby/testdata/ast/empty-alternations.yaml | 9 +++++++ .../ruby/testdata/ast/empty-string.yaml | 3 +++ .../testdata/ast/escaped-alternation.yaml | 5 ++++ .../ruby/testdata/ast/escaped-backslash.yaml | 5 ++++ .../ast/escaped-opening-parenthesis.yaml | 5 ++++ ...escaped-optional-followed-by-optional.yaml | 15 +++++++++++ .../testdata/ast/escaped-optional-phrase.yaml | 10 +++++++ .../ruby/testdata/ast/escaped-optional.yaml | 7 +++++ .../ruby/testdata/ast/opening-brace.yaml | 8 ++++++ .../testdata/ast/opening-parenthesis.yaml | 8 ++++++ .../optional-containing-escaped-optional.yaml | 14 ++++++++++ .../ruby/testdata/ast/optional-phrase.yaml | 12 +++++++++ .../ruby/testdata/ast/optional.yaml | 7 +++++ .../ruby/testdata/ast/parameter.yaml | 7 +++++ .../ruby/testdata/ast/phrase.yaml | 9 +++++++ .../testdata/ast/unfinished-parameter.yaml | 8 ++++++ ...lows-escaped-optional-parameter-types.yaml | 4 +++ ...llows-parameter-type-in-alternation-1.yaml | 4 +++ ...llows-parameter-type-in-alternation-2.yaml | 4 +++ ...low-parameter-adjacent-to-alternation.yaml | 5 ++++ ...lternative-by-adjacent-left-parameter.yaml | 10 +++++++ ...mpty-alternative-by-adjacent-optional.yaml | 9 +++++++ ...ternative-by-adjacent-right-parameter.yaml | 9 +++++++ ...ow-alternation-with-empty-alternative.yaml | 9 +++++++ .../does-not-allow-empty-optional.yaml | 9 +++++++ ...es-not-allow-optional-parameter-types.yaml | 9 +++++++ ...llow-parameter-type-with-left-bracket.yaml | 10 +++++++ .../does-not-match-misquoted-string.yaml | 4 +++ .../expression/doesnt-match-float-as-int.yaml | 5 ++++ ...tches-alternation-in-optional-as-text.yaml | 4 +++ .../expression/matches-alternation.yaml | 4 +++ .../matches-anonymous-parameter-type.yaml | 5 ++++ ...empty-string-along-with-other-strings.yaml | 4 +++ ...e-quoted-empty-string-as-empty-string.yaml | 4 +++ ...oted-string-with-escaped-double-quote.yaml | 4 +++ ...uble-quoted-string-with-single-quotes.yaml | 4 +++ .../matches-double-quoted-string.yaml | 4 +++ .../matches-doubly-escaped-parenthesis.yaml | 4 +++ .../matches-doubly-escaped-slash-1.yaml | 4 +++ .../matches-doubly-escaped-slash-2.yaml | 4 +++ .../matches-escaped-parenthesis-1.yaml | 4 +++ .../matches-escaped-parenthesis-2.yaml | 4 +++ .../matches-escaped-parenthesis-3.yaml | 4 +++ .../expression/matches-escaped-slash.yaml | 4 +++ .../testdata/expression/matches-float-1.yaml | 5 ++++ .../testdata/expression/matches-float-2.yaml | 5 ++++ .../ruby/testdata/expression/matches-int.yaml | 5 ++++ ...atches-multiple-double-quoted-strings.yaml | 4 +++ ...atches-multiple-single-quoted-strings.yaml | 4 +++ ...matches-optional-before-alternation-1.yaml | 4 +++ ...matches-optional-before-alternation-2.yaml | 4 +++ ...e-alternation-with-regex-characters-1.yaml | 4 +++ ...e-alternation-with-regex-characters-2.yaml | 4 +++ .../matches-optional-in-alternation-1.yaml | 5 ++++ .../matches-optional-in-alternation-2.yaml | 5 ++++ .../matches-optional-in-alternation-3.yaml | 5 ++++ ...empty-string-along-with-other-strings.yaml | 4 +++ ...e-quoted-empty-string-as-empty-string.yaml | 4 +++ ...ngle-quoted-string-with-double-quotes.yaml | 4 +++ ...oted-string-with-escaped-single-quote.yaml | 4 +++ .../matches-single-quoted-string.yaml | 4 +++ .../testdata/expression/matches-word.yaml | 4 +++ .../throws-unknown-parameter-type.yaml | 10 +++++++ .../testdata/tokens/alternation-phrase.yaml | 13 +++++++++ .../ruby/testdata/tokens/alternation.yaml | 9 +++++++ .../ruby/testdata/tokens/empty-string.yaml | 6 +++++ .../tokens/escape-non-reserved-character.yaml | 8 ++++++ .../testdata/tokens/escaped-alternation.yaml | 9 +++++++ ...ed-char-has-start-index-of-text-token.yaml | 9 +++++++ .../testdata/tokens/escaped-end-of-line.yaml | 8 ++++++ .../testdata/tokens/escaped-optional.yaml | 7 +++++ .../testdata/tokens/escaped-parameter.yaml | 7 +++++ .../ruby/testdata/tokens/escaped-space.yaml | 7 +++++ .../ruby/testdata/tokens/optional-phrase.yaml | 13 +++++++++ .../ruby/testdata/tokens/optional.yaml | 9 +++++++ .../testdata/tokens/parameter-phrase.yaml | 13 +++++++++ .../ruby/testdata/tokens/parameter.yaml | 9 +++++++ .../ruby/testdata/tokens/phrase.yaml | 11 ++++++++ 92 files changed, 690 insertions(+), 1 deletion(-) create mode 100644 cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb create mode 100644 cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb create mode 100644 cucumber-expressions/ruby/testdata/ast/alternation-followed-by-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/alternation-phrase.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/alternation-with-parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/alternation-with-unused-end-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/alternation-with-unused-start-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/alternation-with-white-space.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/alternation.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/anonymous-parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/closing-brace.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/closing-parenthesis.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/empty-alternation.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/empty-alternations.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/empty-string.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/escaped-alternation.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/escaped-backslash.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/escaped-opening-parenthesis.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/escaped-optional-followed-by-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/escaped-optional-phrase.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/escaped-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/opening-brace.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/opening-parenthesis.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/optional-phrase.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/phrase.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/unfinished-parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/allows-escaped-optional-parameter-types.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/allows-parameter-type-in-alternation-1.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/allows-parameter-type-in-alternation-2.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-empty-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-optional-parameter-types.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-match-misquoted-string.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/doesnt-match-float-as-int.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-alternation-in-optional-as-text.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-alternation.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-anonymous-parameter-type.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-parenthesis.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-slash-1.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-slash-2.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-1.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-2.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-3.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-escaped-slash.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-float-1.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-float-2.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-int.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-multiple-double-quoted-strings.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-multiple-single-quoted-strings.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-1.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-2.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-1.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-2.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-3.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/matches-word.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/throws-unknown-parameter-type.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/alternation-phrase.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/alternation.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/empty-string.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/escape-non-reserved-character.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/escaped-alternation.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/escaped-end-of-line.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/escaped-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/escaped-parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/escaped-space.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/optional-phrase.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/parameter-phrase.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/tokens/phrase.yaml diff --git a/cucumber-expressions/ruby/examples.txt b/cucumber-expressions/ruby/examples.txt index 12bee4dc01..3a8288d34d 100644 --- a/cucumber-expressions/ruby/examples.txt +++ b/cucumber-expressions/ruby/examples.txt @@ -2,7 +2,7 @@ I have {int} cuke(s) I have 22 cukes [22] --- -I have {int} cuke(s) and some \[]^$.|?*+ +I have {int} cuke(s) and some \\[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- @@ -29,3 +29,15 @@ I have 22 cukes in my belly now I have {} cuke(s) in my {} now I have 22 cukes in my belly now ["22","belly"] +--- +/^a (pre-commercial transaction |pre buyer fee model )?purchase(?: for \$(\d+))?$/ +a purchase for $33 +[null,33] +--- +Some ${float} of cukes at {int}° Celsius +Some $3.50 of cukes at 42° Celsius +[3.5,42] +--- +I select the {int}st/nd/rd/th Cucumber +I select the 3rd Cucumber +[3] diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb new file mode 100644 index 0000000000..fcbccbfb52 --- /dev/null +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb @@ -0,0 +1,12 @@ +module Cucumber + module CucumberExpressions + class CucumberExpressionTokenizer + def tokenize(expression) + [ + {"type" => "START_OF_LINE", "start" => 0, "end" => 0, "text" => ""}, + {"type" => "END_OF_LINE", "start" => 0, "end" => 0, "text" => ""} + ] + end + end + end +end diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb new file mode 100644 index 0000000000..efe127f609 --- /dev/null +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb @@ -0,0 +1,25 @@ +require 'yaml' +require 'json' +require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer' +require 'cucumber/cucumber_expressions/errors' + +module Cucumber + module CucumberExpressions + describe 'Cucumber expression tokenizer' do + Dir['testdata/tokens/*.yaml'].each do |testcase| + + expectation = YAML.load_file(testcase) # encoding? + + it "#{testcase}" do + tokenizer = CucumberExpressionTokenizer.new + if expectation['exception'].nil? + tokens = tokenizer.tokenize(expectation['expression']) + expect(tokens).to eq(JSON.parse(expectation['expected'])) + else + expect { tokenizer.tokenize(expectation['expression']) }.to raise_error(CucumberExpressionError, expectation['exception']) + end + end + end + end + end +end diff --git a/cucumber-expressions/ruby/testdata/ast/alternation-followed-by-optional.yaml b/cucumber-expressions/ruby/testdata/ast/alternation-followed-by-optional.yaml new file mode 100644 index 0000000000..0bfb53a534 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/alternation-followed-by-optional.yaml @@ -0,0 +1,17 @@ +expression: three blind\ rat/cat(s) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 16, "token": "blind rat"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 17, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 17, "end": 20, "token": "cat"}, + {"type": "OPTIONAL_NODE", "start": 20, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": "s"} + ]} + ]} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/alternation-phrase.yaml b/cucumber-expressions/ruby/testdata/ast/alternation-phrase.yaml new file mode 100644 index 0000000000..9a243822d5 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/alternation-phrase.yaml @@ -0,0 +1,16 @@ +expression: three hungry/blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 12, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 12, "token": "hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 13, "end": 18, "token": "blind"} + ]} + ]}, + {"type": "TEXT_NODE", "start": 18, "end": 19, "token": " "}, + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "mice"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/alternation-with-parameter.yaml b/cucumber-expressions/ruby/testdata/ast/alternation-with-parameter.yaml new file mode 100644 index 0000000000..c5daf32bd7 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/alternation-with-parameter.yaml @@ -0,0 +1,27 @@ +expression: I select the {int}st/nd/rd/th +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "I"}, + {"type": "TEXT_NODE", "start": 1, "end": 2, "token": " "}, + {"type": "TEXT_NODE", "start": 2, "end": 8, "token": "select"}, + {"type": "TEXT_NODE", "start": 8, "end": 9, "token": " "}, + {"type": "TEXT_NODE", "start": 9, "end": 12, "token": "the"}, + {"type": "TEXT_NODE", "start": 12, "end": 13, "token": " "}, + {"type": "PARAMETER_NODE", "start": 13, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 14, "end": 17, "token": "int"} + ]}, + {"type": "ALTERNATION_NODE", "start": 18, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 18, "end": 20, "nodes": [ + {"type": "TEXT_NODE", "start": 18, "end": 20, "token": "st"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 21, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 21, "end": 23, "token": "nd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 24, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 24, "end": 26, "token": "rd"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 27, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 27, "end": 29, "token": "th"} + ]} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/alternation-with-unused-end-optional.yaml b/cucumber-expressions/ruby/testdata/ast/alternation-with-unused-end-optional.yaml new file mode 100644 index 0000000000..842838b75f --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/alternation-with-unused-end-optional.yaml @@ -0,0 +1,15 @@ +expression: three )blind\ mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "ALTERNATION_NODE", "start": 6, "end": 23, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 6, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 6, "end": 7, "token": ")"}, + {"type": "TEXT_NODE", "start": 7, "end": 18, "token": "blind mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 19, "end": 23, "nodes": [ + {"type": "TEXT_NODE", "start": 19, "end": 23, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/alternation-with-unused-start-optional.yaml b/cucumber-expressions/ruby/testdata/ast/alternation-with-unused-start-optional.yaml new file mode 100644 index 0000000000..e2f0584556 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/alternation-with-unused-start-optional.yaml @@ -0,0 +1,8 @@ +expression: three blind\ mice/rats( +exception: |- + This Cucumber Expression has a problem at column 23: + + three blind\ mice/rats( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/ruby/testdata/ast/alternation-with-white-space.yaml b/cucumber-expressions/ruby/testdata/ast/alternation-with-white-space.yaml new file mode 100644 index 0000000000..eedd57dd21 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/alternation-with-white-space.yaml @@ -0,0 +1,12 @@ +expression: '\ three\ hungry/blind\ mice\ ' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 29, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 15, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 15, "token": " three hungry"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 16, "end": 29, "nodes": [ + {"type": "TEXT_NODE", "start": 16, "end": 29, "token": "blind mice "} + ]} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/alternation.yaml b/cucumber-expressions/ruby/testdata/ast/alternation.yaml new file mode 100644 index 0000000000..88df8325fe --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/alternation.yaml @@ -0,0 +1,12 @@ +expression: mice/rats +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 9, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 4, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 4, "token": "mice"} + ]}, + {"type": "ALTERNATIVE_NODE", "start": 5, "end": 9, "nodes": [ + {"type": "TEXT_NODE", "start": 5, "end": 9, "token": "rats"} + ]} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/anonymous-parameter.yaml b/cucumber-expressions/ruby/testdata/ast/anonymous-parameter.yaml new file mode 100644 index 0000000000..2c4d339333 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/anonymous-parameter.yaml @@ -0,0 +1,5 @@ +expression: "{}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 2, "nodes": []} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/closing-brace.yaml b/cucumber-expressions/ruby/testdata/ast/closing-brace.yaml new file mode 100644 index 0000000000..1bafd9c6a8 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/closing-brace.yaml @@ -0,0 +1,5 @@ +expression: "}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": "}"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/closing-parenthesis.yaml b/cucumber-expressions/ruby/testdata/ast/closing-parenthesis.yaml new file mode 100644 index 0000000000..23daf7bcd3 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/closing-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: ) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 1, "token": ")"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/empty-alternation.yaml b/cucumber-expressions/ruby/testdata/ast/empty-alternation.yaml new file mode 100644 index 0000000000..6d810fc8f3 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/empty-alternation.yaml @@ -0,0 +1,8 @@ +expression: / +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 1, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/empty-alternations.yaml b/cucumber-expressions/ruby/testdata/ast/empty-alternations.yaml new file mode 100644 index 0000000000..f8d4dd4cf8 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/empty-alternations.yaml @@ -0,0 +1,9 @@ +expression: '//' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "ALTERNATIVE_NODE", "start": 0, "end": 0, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 1, "end": 1, "nodes": []}, + {"type": "ALTERNATIVE_NODE", "start": 2, "end": 2, "nodes": []} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/empty-string.yaml b/cucumber-expressions/ruby/testdata/ast/empty-string.yaml new file mode 100644 index 0000000000..4d33c2dc76 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/empty-string.yaml @@ -0,0 +1,3 @@ +expression: "" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 0, "nodes": []} diff --git a/cucumber-expressions/ruby/testdata/ast/escaped-alternation.yaml b/cucumber-expressions/ruby/testdata/ast/escaped-alternation.yaml new file mode 100644 index 0000000000..3ed9c37674 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/escaped-alternation.yaml @@ -0,0 +1,5 @@ +expression: 'mice\/rats' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 10, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 10, "token": "mice/rats"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/escaped-backslash.yaml b/cucumber-expressions/ruby/testdata/ast/escaped-backslash.yaml new file mode 100644 index 0000000000..da2d008e1e --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/escaped-backslash.yaml @@ -0,0 +1,5 @@ +expression: '\\' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "\\"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/escaped-opening-parenthesis.yaml b/cucumber-expressions/ruby/testdata/ast/escaped-opening-parenthesis.yaml new file mode 100644 index 0000000000..afafc59eb8 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/escaped-opening-parenthesis.yaml @@ -0,0 +1,5 @@ +expression: '\(' +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 2, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 2, "token": "("} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/escaped-optional-followed-by-optional.yaml b/cucumber-expressions/ruby/testdata/ast/escaped-optional-followed-by-optional.yaml new file mode 100644 index 0000000000..1e4746291b --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/escaped-optional-followed-by-optional.yaml @@ -0,0 +1,15 @@ +expression: three \((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 8, "token": "("}, + {"type": "OPTIONAL_NODE", "start": 8, "end": 14, "nodes": [ + {"type": "TEXT_NODE", "start": 9, "end": 13, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": ")"}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/escaped-optional-phrase.yaml b/cucumber-expressions/ruby/testdata/ast/escaped-optional-phrase.yaml new file mode 100644 index 0000000000..832249e2a7 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/escaped-optional-phrase.yaml @@ -0,0 +1,10 @@ +expression: three \(blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 19, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 13, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": ")"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 19, "token": "mice"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/escaped-optional.yaml b/cucumber-expressions/ruby/testdata/ast/escaped-optional.yaml new file mode 100644 index 0000000000..4c2b457d6f --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: '\(blind)' + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 7, "token": "(blind"}, + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": ")"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/opening-brace.yaml b/cucumber-expressions/ruby/testdata/ast/opening-brace.yaml new file mode 100644 index 0000000000..916a674a36 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/opening-brace.yaml @@ -0,0 +1,8 @@ +expression: '{' +exception: |- + This Cucumber Expression has a problem at column 1: + + { + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/ruby/testdata/ast/opening-parenthesis.yaml b/cucumber-expressions/ruby/testdata/ast/opening-parenthesis.yaml new file mode 100644 index 0000000000..929d6ae304 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/opening-parenthesis.yaml @@ -0,0 +1,8 @@ +expression: ( +exception: |- + This Cucumber Expression has a problem at column 1: + + ( + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/ruby/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/ruby/testdata/ast/optional-containing-escaped-optional.yaml new file mode 100644 index 0000000000..f09199a454 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/optional-containing-escaped-optional.yaml @@ -0,0 +1,14 @@ +expression: three ((very\) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, + {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, + {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, + {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, + {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/optional-phrase.yaml b/cucumber-expressions/ruby/testdata/ast/optional-phrase.yaml new file mode 100644 index 0000000000..0ef4fb3f95 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/optional-phrase.yaml @@ -0,0 +1,12 @@ +expression: three (blind) mice + +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 18, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 7, "end": 12, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 18, "token": "mice"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/optional.yaml b/cucumber-expressions/ruby/testdata/ast/optional.yaml new file mode 100644 index 0000000000..6ce2b632e7 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/optional.yaml @@ -0,0 +1,7 @@ +expression: (blind) +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 0, "end": 7, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 6, "token": "blind"} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/parameter.yaml b/cucumber-expressions/ruby/testdata/ast/parameter.yaml new file mode 100644 index 0000000000..92ac8c147e --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/parameter.yaml @@ -0,0 +1,7 @@ +expression: "{string}" +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "PARAMETER_NODE", "start": 0, "end": 8, "nodes": [ + {"type": "TEXT_NODE", "start": 1, "end": 7, "token": "string"} + ]} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/phrase.yaml b/cucumber-expressions/ruby/testdata/ast/phrase.yaml new file mode 100644 index 0000000000..ba340d0122 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/phrase.yaml @@ -0,0 +1,9 @@ +expression: three blind mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 16, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "TEXT_NODE", "start": 6, "end": 11, "token": "blind"}, + {"type": "TEXT_NODE", "start": 11, "end": 12, "token": " "}, + {"type": "TEXT_NODE", "start": 12, "end": 16, "token": "mice"} + ]} diff --git a/cucumber-expressions/ruby/testdata/ast/unfinished-parameter.yaml b/cucumber-expressions/ruby/testdata/ast/unfinished-parameter.yaml new file mode 100644 index 0000000000..d02f9b4ccf --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/unfinished-parameter.yaml @@ -0,0 +1,8 @@ +expression: "{string" +exception: |- + This Cucumber Expression has a problem at column 1: + + {string + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/ruby/testdata/expression/allows-escaped-optional-parameter-types.yaml b/cucumber-expressions/ruby/testdata/expression/allows-escaped-optional-parameter-types.yaml new file mode 100644 index 0000000000..a00b45acef --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/allows-escaped-optional-parameter-types.yaml @@ -0,0 +1,4 @@ +expression: \({int}) +text: (3) +expected: |- + [3] diff --git a/cucumber-expressions/ruby/testdata/expression/allows-parameter-type-in-alternation-1.yaml b/cucumber-expressions/ruby/testdata/expression/allows-parameter-type-in-alternation-1.yaml new file mode 100644 index 0000000000..bb1a6f21b1 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/allows-parameter-type-in-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: i18n +expected: |- + [18] diff --git a/cucumber-expressions/ruby/testdata/expression/allows-parameter-type-in-alternation-2.yaml b/cucumber-expressions/ruby/testdata/expression/allows-parameter-type-in-alternation-2.yaml new file mode 100644 index 0000000000..cdddce7d84 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/allows-parameter-type-in-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: a/i{int}n/y +text: a11y +expected: |- + [11] diff --git a/cucumber-expressions/ruby/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml b/cucumber-expressions/ruby/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml new file mode 100644 index 0000000000..9e2ecdfbe1 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml @@ -0,0 +1,5 @@ +expression: |- + {int}st/nd/rd/th +text: 3rd +expected: |- + [3] diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml new file mode 100644 index 0000000000..b32540a4a9 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml @@ -0,0 +1,10 @@ +expression: |- + {int}/x +text: '3' +exception: |- + This Cucumber Expression has a problem at column 6: + + {int}/x + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml new file mode 100644 index 0000000000..a0aab0e5a9 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml @@ -0,0 +1,9 @@ +expression: three (brown)/black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (brown)/black mice + ^-----^ + An alternative may not exclusively contain optionals. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml new file mode 100644 index 0000000000..50250f00aa --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml @@ -0,0 +1,9 @@ +expression: x/{int} +text: '3' +exception: |- + This Cucumber Expression has a problem at column 3: + + x/{int} + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml new file mode 100644 index 0000000000..b724cfa77f --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml @@ -0,0 +1,9 @@ +expression: three brown//black mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three brown//black mice + ^ + Alternative may not be empty. + If you did not mean to use an alternative you can use '\/' to escape the the '/' diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-empty-optional.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-empty-optional.yaml new file mode 100644 index 0000000000..00e341af0b --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-empty-optional.yaml @@ -0,0 +1,9 @@ +expression: three () mice +text: three brown mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three () mice + ^^ + An optional must contain some text. + If you did not mean to use an optional you can use '\(' to escape the the '(' diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-optional-parameter-types.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-optional-parameter-types.yaml new file mode 100644 index 0000000000..b88061e9b4 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-optional-parameter-types.yaml @@ -0,0 +1,9 @@ +expression: ({int}) +text: '3' +exception: |- + This Cucumber Expression has a problem at column 2: + + ({int}) + ^---^ + An optional may not contain a parameter type. + If you did not mean to use an parameter type you can use '\{' to escape the the '{' diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml new file mode 100644 index 0000000000..1dd65aa276 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml @@ -0,0 +1,10 @@ +expression: |- + {[string]} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {[string]} + ^--------^ + Parameter names may not contain '[]()$.|?*+'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-match-misquoted-string.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-match-misquoted-string.yaml new file mode 100644 index 0000000000..18023180af --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-match-misquoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind' mice +expected: |- + null diff --git a/cucumber-expressions/ruby/testdata/expression/doesnt-match-float-as-int.yaml b/cucumber-expressions/ruby/testdata/expression/doesnt-match-float-as-int.yaml new file mode 100644 index 0000000000..d66b586430 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/doesnt-match-float-as-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '1.22' +expected: |- + null diff --git a/cucumber-expressions/ruby/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/ruby/testdata/expression/matches-alternation-in-optional-as-text.yaml new file mode 100644 index 0000000000..15fe78bf53 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-alternation-in-optional-as-text.yaml @@ -0,0 +1,4 @@ +expression: three( brown/black) mice +text: three brown/black mice +expected: |- + [] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-alternation.yaml b/cucumber-expressions/ruby/testdata/expression/matches-alternation.yaml new file mode 100644 index 0000000000..20a9b9a728 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-alternation.yaml @@ -0,0 +1,4 @@ +expression: mice/rats and rats\/mice +text: rats and rats/mice +expected: |- + [] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-anonymous-parameter-type.yaml b/cucumber-expressions/ruby/testdata/expression/matches-anonymous-parameter-type.yaml new file mode 100644 index 0000000000..fc954960df --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-anonymous-parameter-type.yaml @@ -0,0 +1,5 @@ +expression: |- + {} +text: '0.22' +expected: |- + ["0.22"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c3e1962e22 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "" and "handsome" mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..89315b62ef --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "" mice +expected: |- + [""] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml new file mode 100644 index 0000000000..fe30a044c1 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "bl\"nd" mice +expected: |- + ["bl\"nd"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml new file mode 100644 index 0000000000..25fcf304c1 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "'blind'" mice +expected: |- + ["'blind'"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string.yaml b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string.yaml new file mode 100644 index 0000000000..8b0560f332 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-double-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three "blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-parenthesis.yaml b/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-parenthesis.yaml new file mode 100644 index 0000000000..902a084103 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-parenthesis.yaml @@ -0,0 +1,4 @@ +expression: three \\(exceptionally) \\{string} mice +text: three \exceptionally \"blind" mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-slash-1.yaml b/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-slash-1.yaml new file mode 100644 index 0000000000..94e531eca7 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-slash-1.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: 12\ +expected: |- + [] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-slash-2.yaml b/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-slash-2.yaml new file mode 100644 index 0000000000..9c9c735b86 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-doubly-escaped-slash-2.yaml @@ -0,0 +1,4 @@ +expression: 12\\/2020 +text: '2020' +expected: |- + [] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-1.yaml b/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-1.yaml new file mode 100644 index 0000000000..171df89ee1 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-1.yaml @@ -0,0 +1,4 @@ +expression: three \(exceptionally) \{string} mice +text: three (exceptionally) {string} mice +expected: |- + [] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-2.yaml b/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-2.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-2.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-3.yaml b/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-3.yaml new file mode 100644 index 0000000000..340c63e94f --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-escaped-parenthesis-3.yaml @@ -0,0 +1,4 @@ +expression: three \((exceptionally)) \{{string}} mice +text: three (exceptionally) {"blind"} mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-escaped-slash.yaml b/cucumber-expressions/ruby/testdata/expression/matches-escaped-slash.yaml new file mode 100644 index 0000000000..d8b3933399 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-escaped-slash.yaml @@ -0,0 +1,4 @@ +expression: 12\/2020 +text: 12/2020 +expected: |- + [] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-float-1.yaml b/cucumber-expressions/ruby/testdata/expression/matches-float-1.yaml new file mode 100644 index 0000000000..fe7e8b1869 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-float-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '0.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-float-2.yaml b/cucumber-expressions/ruby/testdata/expression/matches-float-2.yaml new file mode 100644 index 0000000000..c1e5894eac --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-float-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {float} +text: '.22' +expected: |- + [0.22] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-int.yaml b/cucumber-expressions/ruby/testdata/expression/matches-int.yaml new file mode 100644 index 0000000000..bcd3763886 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-int.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} +text: '22' +expected: |- + [22] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-multiple-double-quoted-strings.yaml b/cucumber-expressions/ruby/testdata/expression/matches-multiple-double-quoted-strings.yaml new file mode 100644 index 0000000000..6c74bc2350 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-multiple-double-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three "blind" and "crippled" mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-multiple-single-quoted-strings.yaml b/cucumber-expressions/ruby/testdata/expression/matches-multiple-single-quoted-strings.yaml new file mode 100644 index 0000000000..5037821c14 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-multiple-single-quoted-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three 'blind' and 'crippled' mice +expected: |- + ["blind","crippled"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-1.yaml b/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-1.yaml new file mode 100644 index 0000000000..821776715b --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-1.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three brown mice +expected: |- + [] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-2.yaml b/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-2.yaml new file mode 100644 index 0000000000..71b3a341f1 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-2.yaml @@ -0,0 +1,4 @@ +expression: three (brown )mice/rats +text: three rats +expected: |- + [] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml b/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml new file mode 100644 index 0000000000..2632f410ce --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 2 seconds? +expected: |- + [2] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml b/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml new file mode 100644 index 0000000000..7b30f667bc --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml @@ -0,0 +1,4 @@ +expression: I wait {int} second(s)./second(s)? +text: I wait 1 second. +expected: |- + [1] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-1.yaml b/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-1.yaml new file mode 100644 index 0000000000..6574bb4bdf --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-1.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 3 rats +expected: |- + [3] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-2.yaml b/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-2.yaml new file mode 100644 index 0000000000..4eb0f0e1c6 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-2.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 2 mice +expected: |- + [2] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-3.yaml b/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-3.yaml new file mode 100644 index 0000000000..964fa6489e --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-optional-in-alternation-3.yaml @@ -0,0 +1,5 @@ +expression: |- + {int} rat(s)/mouse/mice +text: 1 mouse +expected: |- + [1] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml new file mode 100644 index 0000000000..c963dcf1c7 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml @@ -0,0 +1,4 @@ +expression: three {string} and {string} mice +text: three '' and 'handsome' mice +expected: |- + ["","handsome"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml new file mode 100644 index 0000000000..cff2a2d1ec --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '' mice +expected: |- + [""] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml new file mode 100644 index 0000000000..eb9ed537cd --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three '"blind"' mice +expected: |- + ["\"blind\""] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml new file mode 100644 index 0000000000..4c2b0055b9 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'bl\'nd' mice +expected: |- + ["bl'nd"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string.yaml b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string.yaml new file mode 100644 index 0000000000..6c8f4652a5 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-single-quoted-string.yaml @@ -0,0 +1,4 @@ +expression: three {string} mice +text: three 'blind' mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/ruby/testdata/expression/matches-word.yaml b/cucumber-expressions/ruby/testdata/expression/matches-word.yaml new file mode 100644 index 0000000000..358fd3afd1 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/matches-word.yaml @@ -0,0 +1,4 @@ +expression: three {word} mice +text: three blind mice +expected: |- + ["blind"] diff --git a/cucumber-expressions/ruby/testdata/expression/throws-unknown-parameter-type.yaml b/cucumber-expressions/ruby/testdata/expression/throws-unknown-parameter-type.yaml new file mode 100644 index 0000000000..384e3a48c3 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/throws-unknown-parameter-type.yaml @@ -0,0 +1,10 @@ +expression: |- + {unknown} +text: something +exception: |- + This Cucumber Expression has a problem at column 1: + + {unknown} + ^-------^ + Undefined parameter type 'unknown'. + Please register a ParameterType for 'unknown' diff --git a/cucumber-expressions/ruby/testdata/tokens/alternation-phrase.yaml b/cucumber-expressions/ruby/testdata/tokens/alternation-phrase.yaml new file mode 100644 index 0000000000..48b107f64e --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/alternation-phrase.yaml @@ -0,0 +1,13 @@ +expression: three blind/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "ALTERNATION", "start": 11, "end": 12, "text": "/"}, + {"type": "TEXT", "start": 12, "end": 19, "text": "cripple"}, + {"type": "WHITE_SPACE", "start": 19, "end": 20, "text": " "}, + {"type": "TEXT", "start": 20, "end": 24, "text": "mice"}, + {"type": "END_OF_LINE", "start": 24, "end": 24, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/alternation.yaml b/cucumber-expressions/ruby/testdata/tokens/alternation.yaml new file mode 100644 index 0000000000..a4920f22e5 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/alternation.yaml @@ -0,0 +1,9 @@ +expression: blind/cripple +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "blind"}, + {"type": "ALTERNATION", "start": 5, "end": 6, "text": "/"}, + {"type": "TEXT", "start": 6, "end": 13, "text": "cripple"}, + {"type": "END_OF_LINE", "start": 13, "end": 13, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/empty-string.yaml b/cucumber-expressions/ruby/testdata/tokens/empty-string.yaml new file mode 100644 index 0000000000..501f7522f2 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/empty-string.yaml @@ -0,0 +1,6 @@ +expression: "" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "END_OF_LINE", "start": 0, "end": 0, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/escape-non-reserved-character.yaml b/cucumber-expressions/ruby/testdata/tokens/escape-non-reserved-character.yaml new file mode 100644 index 0000000000..5e206be084 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/escape-non-reserved-character.yaml @@ -0,0 +1,8 @@ +expression: \[ +exception: |- + This Cucumber Expression has a problem at column 2: + + \[ + ^ + Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped. + If you did mean to use an '\' you can use '\\' to escape it diff --git a/cucumber-expressions/ruby/testdata/tokens/escaped-alternation.yaml b/cucumber-expressions/ruby/testdata/tokens/escaped-alternation.yaml new file mode 100644 index 0000000000..7e21f7ad19 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/escaped-alternation.yaml @@ -0,0 +1,9 @@ +expression: blind\ and\ famished\/cripple mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 29, "text": "blind and famished/cripple"}, + {"type": "WHITE_SPACE", "start": 29, "end": 30, "text": " "}, + {"type": "TEXT", "start": 30, "end": 34, "text": "mice"}, + {"type": "END_OF_LINE", "start": 34, "end": 34, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml b/cucumber-expressions/ruby/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml new file mode 100644 index 0000000000..6375ad52a5 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml @@ -0,0 +1,9 @@ +expression: ' \/ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "WHITE_SPACE", "start": 0, "end": 1, "text": " "}, + {"type": "TEXT", "start": 1, "end": 3, "text": "/"}, + {"type": "WHITE_SPACE", "start": 3, "end": 4, "text": " "}, + {"type": "END_OF_LINE", "start": 4, "end": 4, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/escaped-end-of-line.yaml b/cucumber-expressions/ruby/testdata/tokens/escaped-end-of-line.yaml new file mode 100644 index 0000000000..a1bd00fd98 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/escaped-end-of-line.yaml @@ -0,0 +1,8 @@ +expression: \ +exception: |- + This Cucumber Expression has a problem at column 1: + + \ + ^ + The end of line can not be escaped. + You can use '\\' to escape the the '\' diff --git a/cucumber-expressions/ruby/testdata/tokens/escaped-optional.yaml b/cucumber-expressions/ruby/testdata/tokens/escaped-optional.yaml new file mode 100644 index 0000000000..2b365b581c --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/escaped-optional.yaml @@ -0,0 +1,7 @@ +expression: \(blind\) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 9, "text": "(blind)"}, + {"type": "END_OF_LINE", "start": 9, "end": 9, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/escaped-parameter.yaml b/cucumber-expressions/ruby/testdata/tokens/escaped-parameter.yaml new file mode 100644 index 0000000000..2cdac4f35a --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/escaped-parameter.yaml @@ -0,0 +1,7 @@ +expression: \{string\} +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 10, "text": "{string}"}, + {"type": "END_OF_LINE", "start": 10, "end": 10, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/escaped-space.yaml b/cucumber-expressions/ruby/testdata/tokens/escaped-space.yaml new file mode 100644 index 0000000000..912827a941 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/escaped-space.yaml @@ -0,0 +1,7 @@ +expression: '\ ' +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 2, "text": " "}, + {"type": "END_OF_LINE", "start": 2, "end": 2, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/optional-phrase.yaml b/cucumber-expressions/ruby/testdata/tokens/optional-phrase.yaml new file mode 100644 index 0000000000..2ddc6bb502 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/optional-phrase.yaml @@ -0,0 +1,13 @@ +expression: three (blind) mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_OPTIONAL", "start": 6, "end": 7, "text": "("}, + {"type": "TEXT", "start": 7, "end": 12, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 12, "end": 13, "text": ")"}, + {"type": "WHITE_SPACE", "start": 13, "end": 14, "text": " "}, + {"type": "TEXT", "start": 14, "end": 18, "text": "mice"}, + {"type": "END_OF_LINE", "start": 18, "end": 18, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/optional.yaml b/cucumber-expressions/ruby/testdata/tokens/optional.yaml new file mode 100644 index 0000000000..35b1474a7c --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/optional.yaml @@ -0,0 +1,9 @@ +expression: (blind) +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_OPTIONAL", "start": 0, "end": 1, "text": "("}, + {"type": "TEXT", "start": 1, "end": 6, "text": "blind"}, + {"type": "END_OPTIONAL", "start": 6, "end": 7, "text": ")"}, + {"type": "END_OF_LINE", "start": 7, "end": 7, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/parameter-phrase.yaml b/cucumber-expressions/ruby/testdata/tokens/parameter-phrase.yaml new file mode 100644 index 0000000000..5e98055ee6 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/parameter-phrase.yaml @@ -0,0 +1,13 @@ +expression: three {string} mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "BEGIN_PARAMETER", "start": 6, "end": 7, "text": "{"}, + {"type": "TEXT", "start": 7, "end": 13, "text": "string"}, + {"type": "END_PARAMETER", "start": 13, "end": 14, "text": "}"}, + {"type": "WHITE_SPACE", "start": 14, "end": 15, "text": " "}, + {"type": "TEXT", "start": 15, "end": 19, "text": "mice"}, + {"type": "END_OF_LINE", "start": 19, "end": 19, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/parameter.yaml b/cucumber-expressions/ruby/testdata/tokens/parameter.yaml new file mode 100644 index 0000000000..460363c393 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/parameter.yaml @@ -0,0 +1,9 @@ +expression: "{string}" +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "BEGIN_PARAMETER", "start": 0, "end": 1, "text": "{"}, + {"type": "TEXT", "start": 1, "end": 7, "text": "string"}, + {"type": "END_PARAMETER", "start": 7, "end": 8, "text": "}"}, + {"type": "END_OF_LINE", "start": 8, "end": 8, "text": ""} + ] diff --git a/cucumber-expressions/ruby/testdata/tokens/phrase.yaml b/cucumber-expressions/ruby/testdata/tokens/phrase.yaml new file mode 100644 index 0000000000..e2cfccf7b4 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/tokens/phrase.yaml @@ -0,0 +1,11 @@ +expression: three blind mice +expected: |- + [ + {"type": "START_OF_LINE", "start": 0, "end": 0, "text": ""}, + {"type": "TEXT", "start": 0, "end": 5, "text": "three"}, + {"type": "WHITE_SPACE", "start": 5, "end": 6, "text": " "}, + {"type": "TEXT", "start": 6, "end": 11, "text": "blind"}, + {"type": "WHITE_SPACE", "start": 11, "end": 12, "text": " "}, + {"type": "TEXT", "start": 12, "end": 16, "text": "mice"}, + {"type": "END_OF_LINE", "start": 16, "end": 16, "text": ""} + ] From e48f75117c46df42503e261462881d9344b92381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 1 Oct 2020 12:43:01 +0100 Subject: [PATCH 157/183] Update plan --- .../cucumber_expression_tokenizer_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb index efe127f609..ddcbb78777 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb @@ -3,13 +3,17 @@ require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer' require 'cucumber/cucumber_expressions/errors' +# Plan: +# - tokenizer tests +# - parser test +# - cucumber expressions compiler +# - phrases + module Cucumber module CucumberExpressions describe 'Cucumber expression tokenizer' do Dir['testdata/tokens/*.yaml'].each do |testcase| - expectation = YAML.load_file(testcase) # encoding? - it "#{testcase}" do tokenizer = CucumberExpressionTokenizer.new if expectation['exception'].nil? From ff96d71dcafb25d3cd715f34c7eb4975f08cca06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 1 Oct 2020 22:36:58 +0100 Subject: [PATCH 158/183] All tokenizer tests green --- .../lib/cucumber/cucumber_expressions/ast.rb | 167 ++++++++++++++++++ .../cucumber_expression_tokenizer.rb | 96 +++++++++- .../cucumber/cucumber_expressions/errors.rb | 48 +++++ .../cucumber_expression_tokenizer_spec.rb | 6 +- 4 files changed, 311 insertions(+), 6 deletions(-) create mode 100644 cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb new file mode 100644 index 0000000000..3d6346e7f8 --- /dev/null +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb @@ -0,0 +1,167 @@ +module Cucumber + module CucumberExpressions + EscapeCharacter = '\\' + AlternationCharacter = '/' + BeginParameterCharacter = '{' + EndParameterCharacter = '}' + BeginOptionalCharacter = '(' + EndOptionalCharacter = ')' + + # export function symbolOf(token: TokenType): string { + # switch (token) { + # case TokenType.beginOptional: + # return BeginOptionalCharacter + # case TokenType.endOptional: + # return EndOptionalCharacter + # case TokenType.beginParameter: + # return BeginParameterCharacter + # case TokenType.endParameter: + # return EndParameterCharacter + # case TokenType.alternation: + # return AlternationCharacter + # } + # return '' + # } + # + # export function purposeOf(token: TokenType): string { + # switch (token) { + # case TokenType.beginOptional: + # case TokenType.endOptional: + # return 'optional text' + # case TokenType.beginParameter: + # case TokenType.endParameter: + # return 'a parameter' + # case TokenType.alternation: + # return 'alternation' + # } + # return '' + # } + # + # export interface Located { + # readonly start: number + # readonly end: number + # } + # + # export class Node { + # readonly type: NodeType + # readonly nodes?: ReadonlyArray | undefined + # readonly token?: string | undefined + # readonly start: number + # readonly end: number + # + # constructor( + # type: NodeType, + # nodes: ReadonlyArray = undefined, + # token: string = undefined, + # start: number, + # end: number + # ) { + # if (nodes === undefined && token === undefined) { + # throw new Error('Either nodes or token must be defined') + # } + # if (nodes === null || token === null) { + # throw new Error('Either nodes or token may not be null') + # } + # this.type = type + # this.nodes = nodes + # this.token = token + # this.start = start + # this.end = end + # } + # + # text(): string { + # if (this.nodes) { + # return this.nodes.map((value) => value.text()).join('') + # } + # return this.token + # } + # } + # + # export enum NodeType { + # text = 'TEXT_NODE', + # optional = 'OPTIONAL_NODE', + # alternation = 'ALTERNATION_NODE', + # alternative = 'ALTERNATIVE_NODE', + # parameter = 'PARAMETER_NODE', + # expression = 'EXPRESSION_NODE', + # } + # + + class Token + def initialize(type, text, start, _end) + @type, @text, @start, @end = type, text, start, _end + end + + def self.isEscapeCharacter(codepoint) + codepoint.chr == EscapeCharacter + end + + def self.canEscape(codepoint) + c = codepoint.chr + if c == ' ' + # TODO: Unicode whitespace? + return true + end + case c + when EscapeCharacter + true + when AlternationCharacter + true + when BeginParameterCharacter + true + when EndParameterCharacter + true + when BeginOptionalCharacter + true + when EndOptionalCharacter + true + else + false + end + end + + def self.typeOf(codepoint) + c = codepoint.chr + if c == ' ' + # TODO: Unicode whitespace? + return TokenType::WhiteSpace + end + case c + when AlternationCharacter + TokenType::Alternation + when BeginParameterCharacter + TokenType::BeginParameter + when EndParameterCharacter + TokenType::EndParameter + when BeginOptionalCharacter + TokenType::BeginOptional + when EndOptionalCharacter + TokenType::EndOptional + else + TokenType::Text + end + end + + def to_hash + { + "type" => @type, + "text" => @text, + "start" => @start, + "end" => @end + } + end + end + + module TokenType + StartOfLine = 'START_OF_LINE' + EndOfLine = 'END_OF_LINE' + WhiteSpace = 'WHITE_SPACE' + BeginOptional = 'BEGIN_OPTIONAL' + EndOptional = 'END_OPTIONAL' + BeginParameter = 'BEGIN_PARAMETER' + EndParameter = 'END_PARAMETER' + Alternation = 'ALTERNATION' + Text = 'TEXT' + end + end +end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb index fcbccbfb52..0444c0a770 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb @@ -1,11 +1,99 @@ +require 'cucumber/cucumber_expressions/ast' +require 'cucumber/cucumber_expressions/errors' + module Cucumber module CucumberExpressions class CucumberExpressionTokenizer def tokenize(expression) - [ - {"type" => "START_OF_LINE", "start" => 0, "end" => 0, "text" => ""}, - {"type" => "END_OF_LINE", "start" => 0, "end" => 0, "text" => ""} - ] + @expression = expression + tokens = [] + @buffer = [] + previousTokenType = TokenType::StartOfLine + treatAsText = false + @escaped = 0 + @bufferStartIndex = 0 + + codepoints = expression.codepoints + + if codepoints.empty? + tokens.push(Token.new(TokenType::StartOfLine, '', 0, 0)) + end + + codepoints.each do |codepoint| + if !treatAsText && Token.isEscapeCharacter(codepoint) + @escaped += 1 + treatAsText = true + next + end + currentTokenType = tokenTypeOf(codepoint, treatAsText) + treatAsText = false + + if shouldCreateNewToken(previousTokenType, currentTokenType) + token = convertBufferToToken(previousTokenType) + previousTokenType = currentTokenType + @buffer.push(codepoint) + tokens.push(token) + else + previousTokenType = currentTokenType + @buffer.push(codepoint) + end + end + + if @buffer.length > 0 + token = convertBufferToToken(previousTokenType) + tokens.push(token) + end + + if (treatAsText) + raise TheEndOfLineCannotBeEscaped.new(expression) + end + + tokens.push( + Token.new(TokenType::EndOfLine, '', codepoints.length, codepoints.length) + ) + tokens + end + + def convertBufferToToken(tokenType) + escapeTokens = 0 + if (tokenType == TokenType::Text) + escapeTokens = @escaped + @escaped = 0 + end + + consumedIndex = @bufferStartIndex + @buffer.length + escapeTokens + t = Token.new( + tokenType, + @buffer.map{|codepoint| codepoint.chr}.join(''), + @bufferStartIndex, + consumedIndex + ) + @buffer = [] + @bufferStartIndex = consumedIndex + return t + end + + def tokenTypeOf(codepoint, treatAsText) + if !treatAsText + return Token.typeOf(codepoint) + end + if Token.canEscape(codepoint) + return TokenType::Text + end + raise CantEscape.new( + @expression, + @bufferStartIndex + @buffer.length + @escaped + ) + end + + def shouldCreateNewToken(previousTokenType, currentTokenType) + if (currentTokenType != previousTokenType) + return true + end + return ( + currentTokenType != TokenType::WhiteSpace && + currentTokenType != TokenType::Text + ) end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb index 9a480cc698..789de5357c 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb @@ -1,6 +1,54 @@ module Cucumber module CucumberExpressions class CucumberExpressionError < StandardError + + def build_message( + index, + expression, + pointer, + problem, + solution + ) +m = <<-EOF +This Cucumber Expression has a problem at column #{index + 1}: + +#{expression} +#{pointer} +#{problem}. +#{solution} +EOF + m.strip + end + + def pointAt(index) + ' ' * index + '^' + end + + end + + class CantEscape < CucumberExpressionError + def initialize(expression, index) + super(build_message( + index, + expression, + pointAt(index), + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped", + "If you did mean to use an '\\' you can use '\\\\' to escape it" + )) + end + end + + class TheEndOfLineCannotBeEscaped < CucumberExpressionError + def initialize(expression) + index = expression.codepoints.length - 1 + super(build_message( + index, + expression, + pointAt(index), + 'The end of line can not be escaped', + "You can use '\\\\' to escape the the '\\'" + )) + end end class UndefinedParameterTypeError < CucumberExpressionError diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb index ddcbb78777..51cdc6b9b5 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb @@ -18,9 +18,11 @@ module CucumberExpressions tokenizer = CucumberExpressionTokenizer.new if expectation['exception'].nil? tokens = tokenizer.tokenize(expectation['expression']) - expect(tokens).to eq(JSON.parse(expectation['expected'])) + token_hashes = tokens.map{|token| token.to_hash} + expect(token_hashes).to eq(JSON.parse(expectation['expected'])) else - expect { tokenizer.tokenize(expectation['expression']) }.to raise_error(CucumberExpressionError, expectation['exception']) + # tokenizer.tokenize(expectation['expression']) + expect { tokenizer.tokenize(expectation['expression']) }.to raise_error(expectation['exception']) end end end From 7d58abc3a03432e5e450bca38bca8f3060996dd5 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 10 Nov 2020 21:14:37 +0100 Subject: [PATCH 159/183] Implement failing test for Cucumber expression parser --- .../cucumber_expression_parser.rb | 12 +++++++++ .../cucumber_expression_parser_spec.rb | 25 +++++++++++++++++++ .../cucumber_expression_tokenizer_spec.rb | 6 ----- 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb create mode 100644 cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb new file mode 100644 index 0000000000..7aa42b6f74 --- /dev/null +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -0,0 +1,12 @@ +require 'cucumber/cucumber_expressions/ast' +require 'cucumber/cucumber_expressions/errors' + +module Cucumber + module CucumberExpressions + class CucumberExpressionParser + def parse(expression) + {} + end + end + end +end diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb new file mode 100644 index 0000000000..ac20983d69 --- /dev/null +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb @@ -0,0 +1,25 @@ +require 'yaml' +require 'json' +require 'cucumber/cucumber_expressions/cucumber_expression_parser' +require 'cucumber/cucumber_expressions/errors' + +module Cucumber + module CucumberExpressions + describe 'Cucumber expression parser' do + Dir['testdata/ast/*.yaml'].each do |testcase| + expectation = YAML.load_file(testcase) # encoding? + it "#{testcase}" do + parser = CucumberExpressionParser.new + if expectation['exception'].nil? + node = parser.parse(expectation['expression']) + node_hash = node.to_hash + expect(node_hash).to eq(JSON.parse(expectation['expected'])) + else + # tokenizer.tokenize(expectation['expression']) + expect { parser.parse(expectation['expression']) }.to raise_error(expectation['exception']) + end + end + end + end + end +end diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb index 51cdc6b9b5..4ad68aabed 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb @@ -3,12 +3,6 @@ require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer' require 'cucumber/cucumber_expressions/errors' -# Plan: -# - tokenizer tests -# - parser test -# - cucumber expressions compiler -# - phrases - module Cucumber module CucumberExpressions describe 'Cucumber expression tokenizer' do From 88140f79c8cd7aee8000cc5f93c20a6fb32324e8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 10 Nov 2020 22:33:47 +0100 Subject: [PATCH 160/183] Clean up --- .../cucumber_expressions/cucumber_expression_parser_spec.rb | 1 - .../cucumber_expressions/cucumber_expression_tokenizer_spec.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb index ac20983d69..94d1afeeb9 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb @@ -15,7 +15,6 @@ module CucumberExpressions node_hash = node.to_hash expect(node_hash).to eq(JSON.parse(expectation['expected'])) else - # tokenizer.tokenize(expectation['expression']) expect { parser.parse(expectation['expression']) }.to raise_error(expectation['exception']) end end diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb index 4ad68aabed..ce64229352 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb @@ -15,7 +15,6 @@ module CucumberExpressions token_hashes = tokens.map{|token| token.to_hash} expect(token_hashes).to eq(JSON.parse(expectation['expected'])) else - # tokenizer.tokenize(expectation['expression']) expect { tokenizer.tokenize(expectation['expression']) }.to raise_error(expectation['exception']) end end From f792bb92bf52caeeb7d6e850c8ce8bb938d54e5d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 14 Nov 2020 23:03:14 +0100 Subject: [PATCH 161/183] Return expression node --- .../lib/cucumber/cucumber_expressions/ast.rb | 107 ++++++++++-------- .../cucumber_expression_parser.rb | 3 +- 2 files changed, 61 insertions(+), 49 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb index 3d6346e7f8..a383da9dc0 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb @@ -42,56 +42,67 @@ module CucumberExpressions # readonly end: number # } # - # export class Node { - # readonly type: NodeType - # readonly nodes?: ReadonlyArray | undefined - # readonly token?: string | undefined - # readonly start: number - # readonly end: number - # - # constructor( - # type: NodeType, - # nodes: ReadonlyArray = undefined, - # token: string = undefined, - # start: number, - # end: number - # ) { - # if (nodes === undefined && token === undefined) { - # throw new Error('Either nodes or token must be defined') - # } - # if (nodes === null || token === null) { - # throw new Error('Either nodes or token may not be null') - # } - # this.type = type - # this.nodes = nodes - # this.token = token - # this.start = start - # this.end = end - # } - # - # text(): string { - # if (this.nodes) { - # return this.nodes.map((value) => value.text()).join('') - # } - # return this.token - # } - # } - # - # export enum NodeType { - # text = 'TEXT_NODE', - # optional = 'OPTIONAL_NODE', - # alternation = 'ALTERNATION_NODE', - # alternative = 'ALTERNATIVE_NODE', - # parameter = 'PARAMETER_NODE', - # expression = 'EXPRESSION_NODE', - # } - # + class Node + def initialize(type, nodes, token, start, _end) + if nodes.nil? && token.nil? + raise 'Either nodes or token must be defined' + end + @type = type + @nodes = nodes + @token = token + @start = start + @end = _end + end + + def text + if @token.nil? + return @nodes.map { |value| value.text }.join('') + end + return @token + end + + def to_hash + { + "type" => @type, + "nodes" => @nodes.nil? ? @nodes : @nodes.map { |node| node.to_hash }, + "token" => @token, + "start" => @start, + "end" => @end + } + end + end + + module NodeType + Text = 'TEXT_NODE' + Optional = 'OPTIONAL_NODE' + Alternation = 'ALTERNATION_NODE' + Alternative = 'ALTERNATIVE_NODE' + Parameter = 'PARAMETER_NODE' + Expression = 'EXPRESSION_NODE' + end + class Token def initialize(type, text, start, _end) @type, @text, @start, @end = type, text, start, _end end + def type + @type + end + + def text + @text + end + + def start + @start + end + + def end + @end + end + def self.isEscapeCharacter(codepoint) codepoint.chr == EscapeCharacter end @@ -144,10 +155,10 @@ def self.typeOf(codepoint) def to_hash { - "type" => @type, - "text" => @text, - "start" => @start, - "end" => @end + "type" => @type, + "text" => @text, + "start" => @start, + "end" => @end } end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index 7aa42b6f74..6fecfa8f38 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -5,7 +5,8 @@ module Cucumber module CucumberExpressions class CucumberExpressionParser def parse(expression) - {} + token = Token.new(TokenType::Text, expression, 0, expression.length) + Node.new(NodeType::Expression, nil, token.text, token.start, token.end) end end end From d1361e0474e3812f7dc0b97c7c30093444a36d81 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 15 Nov 2020 02:10:23 +0100 Subject: [PATCH 162/183] Transpiled a few methods --- .../cucumber_expression_parser.rb | 107 +++++++++++++++++- .../cucumber_expression_tokenizer.rb | 3 + 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index 6fecfa8f38..9cbb688384 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -1,13 +1,116 @@ require 'cucumber/cucumber_expressions/ast' require 'cucumber/cucumber_expressions/errors' +# text := token +parseText = lambda do |expression, tokens, current| + token = tokens[current] + return 1, [Node.new(NodeType::Text, nil, token.text, token.start, token.end)] +end + +# parameter := '{' + text* + '}' +parseParameter = parseBetween( + NodeType::Parameter, + TokenType::BeginParameter, + TokenType.EndParameter, + [parseText] +) + +# optional := '(' + option* + ')' +# option := parameter | text +parseOptional = parseBetween( + NodeType::Optional, + TokenType::BeginOptional, + TokenType::EndOptional, + [parseParameter, parseText] +) + +# alternation := alternative* + ( '/' + alternative* )+ +parseAlternativeSeparator = lambda do |expression, tokens, current| + unless lookingAt(tokens, current, TokenType::Alternation) + return 0, nil + end + token = tokens[current] + return 1, [Node.new(NodeType::Alternative, nil, token.text, token.start, token.end)] +end + +alternativeParsers = [ + parseAlternativeSeparator, + parseOptional, + parseParameter, + parseText, +] + +# alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary) +# left-boundary := whitespace | } | ^ +# right-boundary := whitespace | { | $ +# alternative: = optional | parameter | text +parseAlternation = lambda do |expression, tokens, current| + previous = current - 1 + unless lookingAtAny(tokens, previous, [TokenType::StartOfLine, TokenType::WhiteSpace, TokenType::EndParameter]) + return 0, nil + end + + consumed, ast = parseTokensUntil(expression, alternativeParsers, tokens, current, [TokenType::WhiteSpace, TokenType::EndOfLine, TokenType::BeginParameter]) + subCurrent = current + consumed + if ast.map { |astNode| astNode.type }.include? NodeType::Alternative + return 0, nil + end + + start = tokens[current].start + _end = tokens[subCurrent].start + # Does not consume right hand boundary token + return consumed, [Node.new(NodeType::Alternation, splitAlternatives(start, _end, ast), nil, start, _end)] +end + +# +# cucumber-expression := ( alternation | optional | parameter | text )* +# +parseCucumberExpression = parseBetween( + NodeType::Expression, + TokenType::StartOfLine, + TokenType::EndOfLine, + [parseAlternation, parseOptional, parseParameter, parseText] +) + module Cucumber module CucumberExpressions class CucumberExpressionParser def parse(expression) - token = Token.new(TokenType::Text, expression, 0, expression.length) - Node.new(NodeType::Expression, nil, token.text, token.start, token.end) + tokenizer = CucumberExpressionTokenizer.new + tokens = tokenizer.tokenize(expression) + _, ast = parseCucumberExpression(expression, tokens, 0) + ast[0] end end end end + +def parseBetween(type, beginToken, endToken, parsers) + lambda do |expression, tokens, current| + unless lookingAt(tokens, current, beginToken) + return 0, nil + end + subCurrent = current + 1 + consumed, ast = parseTokensUntil(expression, parsers, tokens, subCurrent, [endToken]) + subCurrent += consumed + + # endToken not found + unless lookingAt(tokens, subCurrent, endToken) + raise "createMissingEndToken" + # throw createMissingEndToken( + # expression, + # beginToken, + # endToken, + # tokens[current] + # ) + end + # consumes endToken + start = tokens[current].start + _end = tokens[subCurrent].end + consumed = subCurrent + 1 - current + ast = [Node.new(type, ast, nil, start, _end)] + return consumed, ast + end +end + +## Next up parseToken diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb index 0444c0a770..78308ff14f 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb @@ -54,6 +54,9 @@ def tokenize(expression) tokens end + private + # TODO: Make these lambdas + def convertBufferToToken(tokenType) escapeTokens = 0 if (tokenType == TokenType::Text) From 86d6451dfc5df887c9bed2e74d6987a0c1d9e6e2 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 15 Nov 2020 21:47:25 +0100 Subject: [PATCH 163/183] More tests pass --- .../lib/cucumber/cucumber_expressions/ast.rb | 38 ++- .../cucumber_expression_parser.rb | 257 +++++++++++------- 2 files changed, 189 insertions(+), 106 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb index a383da9dc0..7e286e42ae 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb @@ -54,6 +54,26 @@ def initialize(type, nodes, token, start, _end) @end = _end end + def type + @type + end + + def nodes + @nodes + end + + def token + @token + end + + def start + @start + end + + def end + @end + end + def text if @token.nil? return @nodes.map { |value| value.text }.join('') @@ -62,13 +82,17 @@ def text end def to_hash - { - "type" => @type, - "nodes" => @nodes.nil? ? @nodes : @nodes.map { |node| node.to_hash }, - "token" => @token, - "start" => @start, - "end" => @end - } + hash = Hash.new + hash["type"] = @type + unless @nodes.nil? + hash["nodes"] = @nodes.map { |node| node.to_hash } + end + unless @token.nil? + hash["token"] = @token + end + hash["start"] = @start + hash["end"] = @end + hash end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index 9cbb688384..0b3883e1eb 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -1,116 +1,175 @@ require 'cucumber/cucumber_expressions/ast' require 'cucumber/cucumber_expressions/errors' -# text := token -parseText = lambda do |expression, tokens, current| - token = tokens[current] - return 1, [Node.new(NodeType::Text, nil, token.text, token.start, token.end)] -end -# parameter := '{' + text* + '}' -parseParameter = parseBetween( - NodeType::Parameter, - TokenType::BeginParameter, - TokenType.EndParameter, - [parseText] -) - -# optional := '(' + option* + ')' -# option := parameter | text -parseOptional = parseBetween( - NodeType::Optional, - TokenType::BeginOptional, - TokenType::EndOptional, - [parseParameter, parseText] -) - -# alternation := alternative* + ( '/' + alternative* )+ -parseAlternativeSeparator = lambda do |expression, tokens, current| - unless lookingAt(tokens, current, TokenType::Alternation) - return 0, nil - end - token = tokens[current] - return 1, [Node.new(NodeType::Alternative, nil, token.text, token.start, token.end)] -end +module Cucumber + module CucumberExpressions -alternativeParsers = [ - parseAlternativeSeparator, - parseOptional, - parseParameter, - parseText, -] - -# alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary) -# left-boundary := whitespace | } | ^ -# right-boundary := whitespace | { | $ -# alternative: = optional | parameter | text -parseAlternation = lambda do |expression, tokens, current| - previous = current - 1 - unless lookingAtAny(tokens, previous, [TokenType::StartOfLine, TokenType::WhiteSpace, TokenType::EndParameter]) - return 0, nil - end + class CucumberExpressionParser + def parse(expression) + # text := token + parseText = lambda do |expression, tokens, current| + token = tokens[current] + return 1, [Node.new(NodeType::Text, nil, token.text, token.start, token.end)] + end - consumed, ast = parseTokensUntil(expression, alternativeParsers, tokens, current, [TokenType::WhiteSpace, TokenType::EndOfLine, TokenType::BeginParameter]) - subCurrent = current + consumed - if ast.map { |astNode| astNode.type }.include? NodeType::Alternative - return 0, nil - end + # parameter := '{' + text* + '}' + parseParameter = parseBetween( + NodeType::Parameter, + TokenType::BeginParameter, + TokenType::EndParameter, + [parseText] + ) - start = tokens[current].start - _end = tokens[subCurrent].start - # Does not consume right hand boundary token - return consumed, [Node.new(NodeType::Alternation, splitAlternatives(start, _end, ast), nil, start, _end)] -end + # optional := '(' + option* + ')' + # option := parameter | text + parseOptional = parseBetween( + NodeType::Optional, + TokenType::BeginOptional, + TokenType::EndOptional, + [parseParameter, parseText] + ) -# -# cucumber-expression := ( alternation | optional | parameter | text )* -# -parseCucumberExpression = parseBetween( - NodeType::Expression, - TokenType::StartOfLine, - TokenType::EndOfLine, - [parseAlternation, parseOptional, parseParameter, parseText] -) + # alternation := alternative* + ( '/' + alternative* )+ + parseAlternativeSeparator = lambda do |expression, tokens, current| + unless lookingAt(tokens, current, TokenType::Alternation) + return 0, nil + end + token = tokens[current] + return 1, [Node.new(NodeType::Alternative, nil, token.text, token.start, token.end)] + end + + alternativeParsers = [ + parseAlternativeSeparator, + parseOptional, + parseParameter, + parseText, + ] + + # alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary) + # left-boundary := whitespace | } | ^ + # right-boundary := whitespace | { | $ + # alternative: = optional | parameter | text + parseAlternation = lambda do |expression, tokens, current| + previous = current - 1 + unless lookingAtAny(tokens, previous, [TokenType::StartOfLine, TokenType::WhiteSpace, TokenType::EndParameter]) + return 0, nil + end + + consumed, ast = parseTokensUntil(expression, alternativeParsers, tokens, current, [TokenType::WhiteSpace, TokenType::EndOfLine, TokenType::BeginParameter]) + subCurrent = current + consumed + unless ast.map { |astNode| astNode.type }.include? NodeType::Alternative + return 0, nil + end + + start = tokens[current].start + _end = tokens[subCurrent].start + # Does not consume right hand boundary token + return consumed, [Node.new(NodeType::Alternation, splitAlternatives(start, _end, ast), nil, start, _end)] + end + + # + # cucumber-expression := ( alternation | optional | parameter | text )* + # + parseCucumberExpression = parseBetween( + NodeType::Expression, + TokenType::StartOfLine, + TokenType::EndOfLine, + [parseAlternation, parseOptional, parseParameter, parseText] + ) -module Cucumber - module CucumberExpressions - class CucumberExpressionParser - def parse(expression) tokenizer = CucumberExpressionTokenizer.new tokens = tokenizer.tokenize(expression) - _, ast = parseCucumberExpression(expression, tokens, 0) + _, ast = parseCucumberExpression.call(expression, tokens, 0) ast[0] end - end - end -end -def parseBetween(type, beginToken, endToken, parsers) - lambda do |expression, tokens, current| - unless lookingAt(tokens, current, beginToken) - return 0, nil - end - subCurrent = current + 1 - consumed, ast = parseTokensUntil(expression, parsers, tokens, subCurrent, [endToken]) - subCurrent += consumed - - # endToken not found - unless lookingAt(tokens, subCurrent, endToken) - raise "createMissingEndToken" - # throw createMissingEndToken( - # expression, - # beginToken, - # endToken, - # tokens[current] - # ) + private + + def parseBetween(type, beginToken, endToken, parsers) + lambda do |expression, tokens, current| + unless lookingAt(tokens, current, beginToken) + return 0, nil + end + subCurrent = current + 1 + consumed, ast = parseTokensUntil(expression, parsers, tokens, subCurrent, [endToken]) + subCurrent += consumed + + # endToken not found + unless lookingAt(tokens, subCurrent, endToken) + raise "createMissingEndToken" + # throw createMissingEndToken( + # expression, + # beginToken, + # endToken, + # tokens[current] + # ) + end + # consumes endToken + start = tokens[current].start + _end = tokens[subCurrent].end + consumed = subCurrent + 1 - current + ast = [Node.new(type, ast, nil, start, _end)] + return consumed, ast + end + end + + def parseToken(expression, parsers, tokens, startAt) + for parser in parsers do + consumed, ast = parser.call(expression, tokens, startAt) + unless consumed == 0 + return consumed, ast + end + end + # If configured correctly this will never happen + raise 'No eligible parsers for ' + tokens + end + + def parseTokensUntil(expression, parsers, tokens, startAt, endTokens) + current = startAt + size = tokens.length + ast = [] + while current < size do + if lookingAtAny(tokens, current, endTokens) + break + end + consumed, subAst = parseToken(expression, parsers, tokens, current) + if consumed == 0 + # If configured correctly this will never happen + # Keep to avoid infinite loops + raise 'No eligible parsers for ' + tokens + end + current += consumed + ast += subAst + end + return current - startAt, ast + end + + def lookingAtAny(tokens, at, tokenTypes) + for tokenType in tokenTypes + if lookingAt(tokens, at, tokenType) + return true + end + end + return false + end + + def lookingAt(tokens, at, token) + if at < 0 + # If configured correctly this will never happen + # Keep for completeness + return token == TokenType::StartOfLine + end + if at >= tokens.length + return token == TokenType::EndOfLine + end + return tokens[at].type == token + end + + def splitAlternatives(start, _end, alternation) + #TODO + [] + end end - # consumes endToken - start = tokens[current].start - _end = tokens[subCurrent].end - consumed = subCurrent + 1 - current - ast = [Node.new(type, ast, nil, start, _end)] - return consumed, ast end end - -## Next up parseToken From 270cab5e125afa1ab38c24ce4f53d8d07991c4db Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 15 Nov 2020 22:20:50 +0100 Subject: [PATCH 164/183] More pass --- .../cucumber_expression_parser.rb | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index 0b3883e1eb..4026a4f7c5 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -4,7 +4,6 @@ module Cucumber module CucumberExpressions - class CucumberExpressionParser def parse(expression) # text := token @@ -167,8 +166,39 @@ def lookingAt(tokens, at, token) end def splitAlternatives(start, _end, alternation) - #TODO - [] + separators = [] + alternatives = [] + alternative = [] + alternation.each { |n| + if NodeType::Alternative == n.type + separators.push(n) + alternatives.push(alternative) + alternative = [] + else + alternative.push(n) + end + } + alternatives.push(alternative) + return createAlternativeNodes(start, _end, separators, alternatives) + end + + def createAlternativeNodes(start, _end, separators, alternatives) + nodes = [] + for i in 0..alternatives.length - 1 + n = alternatives[i] + if (i == 0) + rightSeparator = separators[i] + nodes.push(Node.new(NodeType::Alternative, n, nil, start, rightSeparator.start)) + elsif i == alternatives.length - 1 + leftSeparator = separators[i - 1] + nodes.push(Node.new(NodeType::Alternative, n, nil, leftSeparator.end, _end)) + else + leftSeparator = separators[i - 1] + rightSeparator = separators[i] + nodes.push(Node.new(NodeType::Alternative, n, nil, leftSeparator.end, rightSeparator.start)) + end + end + return nodes end end end From c8b535c81b03b758834508fcb9a45b86c8d6eac0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 15 Nov 2020 22:23:08 +0100 Subject: [PATCH 165/183] Clean up --- .../cucumber_expressions/cucumber_expression_parser.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index 4026a4f7c5..a5f85c37fa 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -184,9 +184,8 @@ def splitAlternatives(start, _end, alternation) def createAlternativeNodes(start, _end, separators, alternatives) nodes = [] - for i in 0..alternatives.length - 1 - n = alternatives[i] - if (i == 0) + alternatives.each_with_index do |n, i| + if i == 0 rightSeparator = separators[i] nodes.push(Node.new(NodeType::Alternative, n, nil, start, rightSeparator.start)) elsif i == alternatives.length - 1 From 64670cd3a6c318419cc38efc0332a4e26f772184 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 17 Nov 2020 01:06:58 +0100 Subject: [PATCH 166/183] All parser tests pass --- .../lib/cucumber/cucumber_expressions/ast.rb | 69 +++++++++--------- .../cucumber_expression_parser.rb | 8 +-- .../cucumber/cucumber_expressions/errors.rb | 70 +++++++++++++------ 3 files changed, 84 insertions(+), 63 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb index 7e286e42ae..ad531fc01b 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb @@ -7,41 +7,6 @@ module CucumberExpressions BeginOptionalCharacter = '(' EndOptionalCharacter = ')' - # export function symbolOf(token: TokenType): string { - # switch (token) { - # case TokenType.beginOptional: - # return BeginOptionalCharacter - # case TokenType.endOptional: - # return EndOptionalCharacter - # case TokenType.beginParameter: - # return BeginParameterCharacter - # case TokenType.endParameter: - # return EndParameterCharacter - # case TokenType.alternation: - # return AlternationCharacter - # } - # return '' - # } - # - # export function purposeOf(token: TokenType): string { - # switch (token) { - # case TokenType.beginOptional: - # case TokenType.endOptional: - # return 'optional text' - # case TokenType.beginParameter: - # case TokenType.endParameter: - # return 'a parameter' - # case TokenType.alternation: - # return 'alternation' - # } - # return '' - # } - # - # export interface Located { - # readonly start: number - # readonly end: number - # } - # class Node def initialize(type, nodes, token, start, _end) if nodes.nil? && token.nil? @@ -177,6 +142,40 @@ def self.typeOf(codepoint) end end + def self.symbolOf(token) + case token + when TokenType::BeginOptional + return BeginOptionalCharacter + when TokenType::EndOptional + return EndOptionalCharacter + when TokenType::BeginParameter + return BeginParameterCharacter + when TokenType::EndParameter + return EndParameterCharacter + when TokenType::Alternation + return AlternationCharacter + else + return '' + end + end + + def self.purposeOf(token) + case token + when TokenType::BeginOptional + return 'optional text' + when TokenType::EndOptional + return 'optional text' + when TokenType::BeginParameter + return 'a parameter' + when TokenType::EndParameter + return 'a parameter' + when TokenType::Alternation + return 'alternation' + else + return '' + end + end + def to_hash { "type" => @type, diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index a5f85c37fa..f1277d5bde 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -96,13 +96,7 @@ def parseBetween(type, beginToken, endToken, parsers) # endToken not found unless lookingAt(tokens, subCurrent, endToken) - raise "createMissingEndToken" - # throw createMissingEndToken( - # expression, - # beginToken, - # endToken, - # tokens[current] - # ) + raise MissingEndToken.new(expression, beginToken, endToken, tokens[current]) end # consumes endToken start = tokens[current].start diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb index 789de5357c..eb1a9e8260 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb @@ -1,22 +1,24 @@ +require 'cucumber/cucumber_expressions/ast' + module Cucumber module CucumberExpressions class CucumberExpressionError < StandardError def build_message( - index, - expression, - pointer, - problem, - solution + index, + expression, + pointer, + problem, + solution ) -m = <<-EOF + m = <<-EOF This Cucumber Expression has a problem at column #{index + 1}: #{expression} #{pointer} #{problem}. #{solution} -EOF + EOF m.strip end @@ -24,17 +26,28 @@ def pointAt(index) ' ' * index + '^' end + def pointAtLocated(node) + pointer = [pointAt(node.start)] + if node.start + 1 < node.end + for _ in node.start + 1...node.end - 2 + pointer.push('-') + end + pointer.push('^') + end + pointer.join('') + end + end class CantEscape < CucumberExpressionError def initialize(expression, index) super(build_message( - index, - expression, - pointAt(index), - "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped", - "If you did mean to use an '\\' you can use '\\\\' to escape it" - )) + index, + expression, + pointAt(index), + "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped", + "If you did mean to use an '\\' you can use '\\\\' to escape it" + )) end end @@ -42,12 +55,27 @@ class TheEndOfLineCannotBeEscaped < CucumberExpressionError def initialize(expression) index = expression.codepoints.length - 1 super(build_message( - index, - expression, - pointAt(index), - 'The end of line can not be escaped', - "You can use '\\\\' to escape the the '\\'" - )) + index, + expression, + pointAt(index), + 'The end of line can not be escaped', + "You can use '\\\\' to escape the the '\\'" + )) + end + end + + class MissingEndToken < CucumberExpressionError + def initialize(expression, beginToken, endToken, current) + beginSymbol = Token::symbolOf(beginToken) + endSymbol = Token::symbolOf(endToken) + purpose = Token::purposeOf(beginToken) + super(build_message( + current.start, + expression, + pointAtLocated(current), + "The '#{beginSymbol}' does not have a matching '#{endSymbol}'", + "If you did not intend to use #{purpose} you can use '\\#{beginSymbol}' to escape the #{purpose}" + )) end end @@ -77,11 +105,11 @@ def initialize(parameter_type_regexp, expression_regexp, parameter_types, genera private def parameter_type_names(parameter_types) - parameter_types.map{|p| "{#{p.name}}"}.join("\n ") + parameter_types.map { |p| "{#{p.name}}" }.join("\n ") end def expressions(generated_expressions) - generated_expressions.map{|ge| ge.source}.join("\n ") + generated_expressions.map { |ge| ge.source }.join("\n ") end end end From 0221ba64c9f7540b605ca41cac31ca5ac28ef93d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 18 Nov 2020 23:24:32 +0100 Subject: [PATCH 167/183] Failing tests for cucumber expression --- .../cucumber_expression_spec.rb | 220 +++++++----------- 1 file changed, 84 insertions(+), 136 deletions(-) diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb index 1de4ed4eef..75043434d5 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb @@ -4,6 +4,25 @@ module Cucumber module CucumberExpressions describe CucumberExpression do + + Dir['testdata/expression/*.yaml'].each do |testcase| + expectation = YAML.load_file(testcase) # encoding? + it "#{testcase}" do + parameter_registry = ParameterTypeRegistry.new + if expectation['exception'].nil? + cucumber_expression = CucumberExpression.new(expectation['expression'], parameter_registry) + matches = cucumber_expression.match(expectation['text']) + values = matches.nil? ? nil : matches.map { |arg| arg.value(nil) } + expect(values).to eq(JSON.parse(expectation['expected'])) + else + expect { + cucumber_expression = CucumberExpression.new(expectation['expression'], parameter_registry) + cucumber_expression.match(expectation['text']) + }.to raise_error(expectation['exception']) + end + end + end + it "documents match arguments" do parameter_registry = ParameterTypeRegistry.new @@ -15,82 +34,6 @@ module CucumberExpressions ### [capture-match-arguments] end - it "matches word" do - expect(match("three {word} mice", "three blind mice")).to eq(['blind']) - end - - it('matches double quoted string') do - expect(match('three {string} mice', 'three "blind" mice')).to eq(['blind']) - end - - it('matches multiple double quoted strings') do - expect(match('three {string} and {string} mice', 'three "blind" and "crippled" mice')).to eq(['blind', 'crippled']) - end - - it('matches single quoted string') do - expect(match('three {string} mice', "three 'blind' mice")).to eq(['blind']) - end - - it('matches multiple single quoted strings') do - expect(match('three {string} and {string} mice', "three 'blind' and 'crippled' mice")).to eq(['blind', 'crippled']) - end - - it('does not match misquoted string') do - expect(match('three {string} mice', 'three "blind\' mice')).to eq(nil) - end - - it('matches single quoted string with double quotes') do - expect(match('three {string} mice', 'three \'"blind"\' mice')).to eq(['"blind"']) - end - - it('matches double quoted string with single quotes') do - expect(match('three {string} mice', 'three "\'blind\'" mice')).to eq(["'blind'"]) - end - - it('matches double quoted string with escaped double quote') do - expect(match('three {string} mice', 'three "bl\\"nd" mice')).to eq(['bl"nd']) - end - - it('matches single quoted string with escaped single quote') do - expect(match('three {string} mice', "three 'bl\\'nd' mice")).to eq(["bl'nd"]) - end - - it('matches single quoted empty string as empty string') do - expect(match('three {string} mice', "three '' mice")).to eq(['']) - end - - it('matches double quoted empty string as empty string') do - expect(match('three {string} mice', 'three "" mice')).to eq(['']) - end - - it('matches single quoted empty string as empty string, along with other strings') do - expect(match('three {string} and {string} mice', "three '' and 'handsome' mice")).to eq(['', 'handsome']) - end - - it('matches double quoted empty string as empty string, along with other strings') do - expect(match('three {string} and {string} mice', 'three "" and "handsome" mice')).to eq(['', 'handsome']) - end - - it 'matches escaped parentheses' do - expect(match('three \\(exceptionally) {string} mice', 'three (exceptionally) "blind" mice')).to eq(['blind']) - end - - it "matches escaped slash" do - expect(match("12\\/2020", "12/2020")).to eq([]) - end - - it "matches int" do - expect(match("{int}", "22")).to eq([22]) - end - - it "doesn't match float as int" do - expect(match("{int}", "1.22")).to be_nil - end - - it "matches int as float" do - expect(match("{float}", "0")).to eq([0.0]) - end - it "matches float" do expect(match("{float}", "")).to eq(nil) expect(match("{float}", ".")).to eq(nil) @@ -126,58 +69,65 @@ module CucumberExpressions expect(match("{float}", "-.1E2")).to eq([-10]) end - it "matches anonymous" do - expect(match("{}", "0.22")).to eq(["0.22"]) - end - '[]()$.|?*+'.split('').each do |char| - it "does not allow parameter type with #{char}" do - expect {match("{#{char}string}", "something")}.to raise_error("Illegal character '#{char}' in parameter name {#{char}string}") - end + it "float with zero" do + expect(match("{float}", "0")).to eq([0.0]) end - it "throws unknown parameter type" do - expect {match("{unknown}", "something")}.to raise_error('Undefined parameter type {unknown}') + it "matches anonymous" do + expect(match("{}", "0.22")).to eq(["0.22"]) end - it "does not allow optional parameter types" do - expect {match("({int})", "3")}.to raise_error('Parameter types cannot be optional: ({int})') + it "exposes source" do + expr = "I have {int} cuke(s)" + expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr) end - it "does allow escaped optional parameter types" do - expect(match("\\({int})", "(3)")).to eq([3]) - end + it "unmatched optional groups have undefined values" do + parameter_type_registry = ParameterTypeRegistry.new + parameter_type_registry.define_parameter_type( + ParameterType.new( + 'textAndOrNumber', + /([A-Z]+)?(?: )?([0-9]+)?/, + Object, + -> (s1, s2) { + [s1, s2] + }, + false, + true + ) + ) + expression = CucumberExpression.new( + '{textAndOrNumber}', + parameter_type_registry + ) - it "does not allow text/parameter type alternation" do - expect {match("x/{int}", "3")}.to raise_error('Parameter types cannot be alternative: x/{int}') - end + class World + end - it "does not allow parameter type/text alternation" do - expect {match("{int}/x", "3")}.to raise_error('Parameter types cannot be alternative: {int}/x') + expect(expression.match("TLA")[0].value(World.new)).to eq(["TLA", nil]) + expect(expression.match("123")[0].value(World.new)).to eq([nil, "123"]) end - it "exposes source" do - expr = "I have {int} cuke(s)" - expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr) - end + # Ruby specific it "delegates transform to custom object" do parameter_type_registry = ParameterTypeRegistry.new parameter_type_registry.define_parameter_type( - ParameterType.new( - 'widget', - /\w+/, - Object, - -> (s) { - self.create_widget(s) - }, - false, - true - ) + ParameterType.new( + 'widget', + /\w+/, + Object, + -> (s) { + self.create_widget(s) + }, + false, + true + ) ) expression = CucumberExpression.new( - 'I have a {widget}', - parameter_type_registry + 'I have a {widget}', + parameter_type_registry ) class World @@ -191,7 +141,7 @@ def create_widget(s) end describe "escapes special characters" do - %w(\\ [ ] ^ $ . | ? * +).each do |character| + %w([ ] ^ $ . | ? * +).each do |character| it "escapes #{character}" do expr = "I have {int} cuke(s) and #{character}" expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) @@ -199,40 +149,38 @@ def create_widget(s) expect(arg1.value(nil)).to eq(800) end end - end + it "escapes ." do + expr = "I have {int} cuke(s) and ." + expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) + expect(expression.match("I have 800 cukes and 3")).to eq(nil) + arg1 = expression.match("I have 800 cukes and .")[0] + expect(arg1.value(nil)).to eq(800) + end - it "unmatched optional groups have undefined values" do - parameter_type_registry = ParameterTypeRegistry.new - parameter_type_registry.define_parameter_type( - ParameterType.new( - 'textAndOrNumber', - /([A-Z]+)?(?: )?([0-9]+)?/, - Object, - -> (s1, s2) { - [s1, s2] - }, - false, - true - ) - ) - expression = CucumberExpression.new( - '{textAndOrNumber}', - parameter_type_registry - ) - - class World + it "escapes \\" do + expr = "I have {int} cuke(s) and \\\\" + expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) + expect(expression.match("I have 800 cukes and 3")).to eq(nil) + arg1 = expression.match("I have 800 cukes and \\")[0] + expect(arg1.value(nil)).to eq(800) end - expect(expression.match("TLA")[0].value(World.new)).to eq(["TLA", nil]) - expect(expression.match("123")[0].value(World.new)).to eq([nil, "123"]) + it "escapes |" do + expr = "I have {int} cuke(s) and a|b" + expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) + expect(expression.match("I have 800 cukes and a")).to eq(nil) + expect(expression.match("I have 800 cukes and b")).to eq(nil) + arg1 = expression.match("I have 800 cukes and a|b")[0] + expect(arg1.value(nil)).to eq(800) + end end def match(expression, text) cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new) args = cucumber_expression.match(text) return nil if args.nil? - args.map {|arg| arg.value(nil)} + args.map { |arg| arg.value(nil) } end end end From 4ce15dc146260c160ff644d1847373d73c818be7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 21 Nov 2020 18:57:02 +0100 Subject: [PATCH 168/183] Some tests pass --- .../CucumberExpression.java | 1 + .../CucumberExpressionTest.java | 2 - .../javascript/src/CucumberExpression.ts | 1 + .../lib/cucumber/cucumber_expressions/ast.rb | 6 +- .../cucumber_expression.rb | 153 +++++++++--------- .../cucumber_expression_tokenizer.rb | 2 +- 6 files changed, 83 insertions(+), 82 deletions(-) diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 77547e2dd5..9a7c017114 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -54,6 +54,7 @@ private String rewriteToRegex(Node node) { case EXPRESSION_NODE: return rewriteExpression(node); default: + // Can't happen as long as the switch case is exhaustive throw new IllegalArgumentException(node.type().name()); } } diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 6857d3724d..eeeee2655a 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -1,7 +1,5 @@ package io.cucumber.cucumberexpressions; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.ExclusionStrategy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.hamcrest.Matchers; diff --git a/cucumber-expressions/javascript/src/CucumberExpression.ts b/cucumber-expressions/javascript/src/CucumberExpression.ts index 1c59c10ac3..4d36383ff0 100644 --- a/cucumber-expressions/javascript/src/CucumberExpression.ts +++ b/cucumber-expressions/javascript/src/CucumberExpression.ts @@ -50,6 +50,7 @@ export default class CucumberExpression implements Expression { case NodeType.expression: return this.rewriteExpression(node) default: + // Can't happen as long as the switch case is exhaustive throw new Error(node.type) } } diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb index ad531fc01b..588367e84f 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb @@ -93,11 +93,11 @@ def end end def self.isEscapeCharacter(codepoint) - codepoint.chr == EscapeCharacter + codepoint.chr(Encoding::UTF_8) == EscapeCharacter end def self.canEscape(codepoint) - c = codepoint.chr + c = codepoint.chr(Encoding::UTF_8) if c == ' ' # TODO: Unicode whitespace? return true @@ -121,7 +121,7 @@ def self.canEscape(codepoint) end def self.typeOf(codepoint) - c = codepoint.chr + c = codepoint.chr(Encoding::UTF_8) if c == ' ' # TODO: Unicode whitespace? return TokenType::WhiteSpace diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb index e7fb7123a4..e33396dd03 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb @@ -5,34 +5,27 @@ module Cucumber module CucumberExpressions class CucumberExpression - # Does not include (){} characters because they have special meaning - ESCAPE_REGEXP = /([\\^\[$.|?*+\]])/ - PARAMETER_REGEXP = /(\\\\)?{([^}]*)}/ - OPTIONAL_REGEXP = /(\\\\)?\(([^)]+)\)/ - ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = /([^\s^\/]+)((\/[^\s^\/]+)+)/ - DOUBLE_ESCAPE = '\\\\' - PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = 'Parameter types cannot be alternative: ' - PARAMETER_TYPES_CANNOT_BE_OPTIONAL = 'Parameter types cannot be optional: ' - attr_reader :source + ESCAPE_PATTERN = /([\\^\[({$.|?*+})\]])/ def initialize(expression, parameter_type_registry) @source = expression @parameter_types = [] - - expression = process_escapes(expression) - expression = process_optional(expression) - expression = process_alternation(expression) - expression = process_parameters(expression, parameter_type_registry) - expression = "^#{expression}$" - - @tree_regexp = TreeRegexp.new(expression) + @parameter_type_registry = parameter_type_registry + parser = CucumberExpressionParser.new + ast = parser.parse(expression) + pattern = rewriteToRegex(ast) + @tree_regexp = TreeRegexp.new(pattern) end def match(text) Argument.build(@tree_regexp, text, @parameter_types) end + def source + @source + end + def regexp @tree_regexp.regexp end @@ -43,76 +36,84 @@ def to_s private - def process_escapes(expression) - expression.gsub(ESCAPE_REGEXP, '\\\\\1') + def rewriteToRegex(node) + case node.type + when NodeType::Text + return escapeRegex(node.text) + when NodeType::Optional + return rewriteOptional(node) + when NodeType::Alternation + return rewriteAlternation(node) + when NodeType::Alternative + return rewriteAlternative(node) + when NodeType::Parameter + return rewriteParameter(node) + when NodeType::Expression + return rewriteExpression(node) + else + # Can't happen as long as the switch case is exhaustive + raise "#{node.type}" + end end - def process_optional(expression) - # Create non-capturing, optional capture groups from parenthesis - expression.gsub(OPTIONAL_REGEXP) do - g2 = $2 - # When using Parameter Types, the () characters are used to represent an optional - # item such as (a ) which would be equivalent to (?:a )? in regex - # - # You cannot have optional Parameter Types i.e. ({int}) as this causes - # problems during the conversion phase to regex. So we check for that here - # - # One exclusion to this rule is if you actually want the brackets i.e. you - # want to capture (3) then we still permit this as an individual rule - # See: https://github.com/cucumber/cucumber-ruby/issues/1337 for more info - # look for double-escaped parentheses - if $1 == DOUBLE_ESCAPE - "\\(#{g2}\\)" - else - check_no_parameter_type(g2, PARAMETER_TYPES_CANNOT_BE_OPTIONAL) - "(?:#{g2})?" - end - end + def escapeRegex(expression) + expression.gsub(ESCAPE_PATTERN, '\\\\\1') end - def process_alternation(expression) - expression.gsub(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP) do - # replace \/ with / - # replace / with | - replacement = $&.tr('/', '|').gsub(/\\\|/, '/') - if replacement.include?('|') - replacement.split(/\|/).each do |part| - check_no_parameter_type(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE) - end - "(?:#{replacement})" - else - replacement - end - end + def rewriteOptional(node) + # this.assertNoParameters(node, (astNode) => + # createParameterIsNotAllowedInOptional(astNode, this.expression) + # ) + # this.assertNotEmpty(node, (astNode) => + # createOptionalMayNotBeEmpty(astNode, this.expression) + # ) + regex = node.nodes.map { |n| rewriteToRegex(n) }.join('') + "(?:#{regex})?" end - def process_parameters(expression, parameter_type_registry) - # Create non-capturing, optional capture groups from parenthesis - expression.gsub(PARAMETER_REGEXP) do - if ($1 == DOUBLE_ESCAPE) - "\\{#{$2}\\}" - else - type_name = $2 - ParameterType.check_parameter_type_name(type_name) - parameter_type = parameter_type_registry.lookup_by_type_name(type_name) - raise UndefinedParameterTypeError.new(type_name) if parameter_type.nil? - @parameter_types.push(parameter_type) - - build_capture_regexp(parameter_type.regexps) - end - end + def rewriteAlternation(node) + # // Make sure the alternative parts aren't empty and don't contain parameter types + # node.nodes.forEach((alternative) => { + # if (alternative.nodes.length == 0) { + # throw createAlternativeMayNotBeEmpty(alternative, this.expression) + # } + # this.assertNotEmpty(alternative, (astNode) => + # createAlternativeMayNotExclusivelyContainOptionals( + # astNode, + # this.expression + # ) + # ) + # }) + regex = node.nodes.map { |n| rewriteToRegex(n) }.join('|') + "(?:#{regex})" end - def build_capture_regexp(regexps) - return "(#{regexps[0]})" if regexps.size == 1 - capture_groups = regexps.map { |group| "(?:#{group})" } - "(#{capture_groups.join('|')})" + + def rewriteAlternative(node) + node.nodes.map { |lastNode| rewriteToRegex(lastNode) }.join('') end - def check_no_parameter_type(s, message) - if PARAMETER_REGEXP =~ s - raise CucumberExpressionError.new("#{message}#{source}") + def rewriteParameter(node) + name = node.text + # if (!ParameterType.isValidParameterTypeName(name)) { + # throw createInvalidParameterTypeName(name) + # } + + parameterType = @parameter_type_registry.lookup_by_type_name(name) + if parameterType.nil? + raise UndefinedParameterTypeError.new(name) end + @parameter_types.push(parameterType) + regexps = parameterType.regexps + if regexps.length == 1 + return "(#{regexps[0]})" + end + return "((?:#{regexps.join(')|(?:')}))" + end + + def rewriteExpression(node) + regex = node.nodes.map { |n| rewriteToRegex(n) }.join('') + "^#{regex}$" end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb index 78308ff14f..50e95fd50b 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb @@ -67,7 +67,7 @@ def convertBufferToToken(tokenType) consumedIndex = @bufferStartIndex + @buffer.length + escapeTokens t = Token.new( tokenType, - @buffer.map{|codepoint| codepoint.chr}.join(''), + @buffer.map{|codepoint| codepoint.chr(Encoding::UTF_8)}.join(''), @bufferStartIndex, consumedIndex ) From 69f00886925bbff2f2bb1673dda21de57d8334ae Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 21 Nov 2020 19:40:21 +0100 Subject: [PATCH 169/183] More tests pass --- .../cucumber_expression.rb | 50 ++++++++-------- .../cucumber/cucumber_expressions/errors.rb | 58 ++++++++++++++++++- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb index e33396dd03..76b312865e 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb @@ -9,7 +9,7 @@ class CucumberExpression ESCAPE_PATTERN = /([\\^\[({$.|?*+})\]])/ def initialize(expression, parameter_type_registry) - @source = expression + @expression = expression @parameter_types = [] @parameter_type_registry = parameter_type_registry parser = CucumberExpressionParser.new @@ -23,7 +23,7 @@ def match(text) end def source - @source + @expression end def regexp @@ -61,34 +61,24 @@ def escapeRegex(expression) end def rewriteOptional(node) - # this.assertNoParameters(node, (astNode) => - # createParameterIsNotAllowedInOptional(astNode, this.expression) - # ) - # this.assertNotEmpty(node, (astNode) => - # createOptionalMayNotBeEmpty(astNode, this.expression) - # ) + assertNoParameters(node, lambda { |astNode| ParameterIsNotAllowedInOptional.new(astNode, @expression) }) + assertNotEmpty(node, lambda { |astNode| OptionalMayNotBeEmpty.new(astNode, @expression) }) regex = node.nodes.map { |n| rewriteToRegex(n) }.join('') "(?:#{regex})?" end def rewriteAlternation(node) - # // Make sure the alternative parts aren't empty and don't contain parameter types - # node.nodes.forEach((alternative) => { - # if (alternative.nodes.length == 0) { - # throw createAlternativeMayNotBeEmpty(alternative, this.expression) - # } - # this.assertNotEmpty(alternative, (astNode) => - # createAlternativeMayNotExclusivelyContainOptionals( - # astNode, - # this.expression - # ) - # ) - # }) + # Make sure the alternative parts aren't empty and don't contain parameter types + node.nodes.each { |alternative| + if alternative.nodes.length == 0 + raise AlternativeMayNotBeEmpty.new(alternative, @expression) + end + assertNotEmpty(alternative, lambda {|astNode| AlternativeMayNotExclusivelyContainOptionals.new(astNode, @expression)}) + } regex = node.nodes.map { |n| rewriteToRegex(n) }.join('|') "(?:#{regex})" end - def rewriteAlternative(node) node.nodes.map { |lastNode| rewriteToRegex(lastNode) }.join('') end @@ -101,20 +91,34 @@ def rewriteParameter(node) parameterType = @parameter_type_registry.lookup_by_type_name(name) if parameterType.nil? - raise UndefinedParameterTypeError.new(name) + raise UndefinedParameterTypeError.new(node, @expression, name) end @parameter_types.push(parameterType) regexps = parameterType.regexps if regexps.length == 1 return "(#{regexps[0]})" end - return "((?:#{regexps.join(')|(?:')}))" + "((?:#{regexps.join(')|(?:')}))" end def rewriteExpression(node) regex = node.nodes.map { |n| rewriteToRegex(n) }.join('') "^#{regex}$" end + + def assertNotEmpty(node, createNodeWasNotEmptyException) + textNodes = node.nodes.filter { |astNode| NodeType::Text == astNode.type } + if textNodes.length == 0 + raise createNodeWasNotEmptyException.call(node) + end + end + + def assertNoParameters(node, createNodeContainedAParameterError) + parameterNodes = node.nodes.filter { |astNode| NodeType::Parameter == astNode.type } + if parameterNodes.length > 0 + raise createNodeContainedAParameterError.call(node) + end + end end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb index eb1a9e8260..3313cab31c 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb @@ -29,7 +29,7 @@ def pointAt(index) def pointAtLocated(node) pointer = [pointAt(node.start)] if node.start + 1 < node.end - for _ in node.start + 1...node.end - 2 + for _ in node.start + 1...node.end - 1 pointer.push('-') end pointer.push('^') @@ -39,6 +39,30 @@ def pointAtLocated(node) end + class AlternativeMayNotExclusivelyContainOptionals < CucumberExpressionError + def initialize(node, expression) + super(build_message( + node.start, + expression, + pointAtLocated(node), + 'An alternative may not exclusively contain optionals', + "If you did not mean to use an optional you can use '\\(' to escape the the '('" + )) + end + end + + class AlternativeMayNotBeEmpty < CucumberExpressionError + def initialize(node, expression) + super(build_message( + node.start, + expression, + pointAtLocated(node), + 'Alternative may not be empty', + "If you did not mean to use an alternative you can use '\\/' to escape the the '/'" + )) + end + end + class CantEscape < CucumberExpressionError def initialize(expression, index) super(build_message( @@ -51,6 +75,30 @@ def initialize(expression, index) end end + class OptionalMayNotBeEmpty < CucumberExpressionError + def initialize(node, expression) + super(build_message( + node.start, + expression, + pointAtLocated(node), + 'An optional must contain some text', + "If you did not mean to use an optional you can use '\\(' to escape the the '('" + )) + end + end + + class ParameterIsNotAllowedInOptional < CucumberExpressionError + def initialize(node, expression) + super(build_message( + node.start, + expression, + pointAtLocated(node), + 'An optional may not contain a parameter type', + "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'" + )) + end + end + class TheEndOfLineCannotBeEscaped < CucumberExpressionError def initialize(expression) index = expression.codepoints.length - 1 @@ -80,8 +128,12 @@ def initialize(expression, beginToken, endToken, current) end class UndefinedParameterTypeError < CucumberExpressionError - def initialize(type_name) - super("Undefined parameter type {#{type_name}}") + def initialize(node, expression, parameter_type_name) + super(build_message(node.start, + expression, + pointAtLocated(node), + "Undefined parameter type '#{parameter_type_name}'", + "Please register a ParameterType for '#{parameter_type_name}'")) end end From 20c0c5b68c9f987b24a7d9c9a8dc9d6c7e7db48b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 21 Nov 2020 20:24:02 +0100 Subject: [PATCH 170/183] All test pass --- .../javascript/src/CucumberExpression.ts | 2 +- .../cucumber_expression.rb | 9 +- .../cucumber_expression_parser.rb | 1 + .../cucumber/cucumber_expressions/errors.rb | 14 +- .../cucumber_expressions/parameter_type.rb | 19 +- .../cucumber_expression_spec.rb | 162 +----------------- .../custom_parameter_type_spec.rb | 2 +- .../expression_examples_spec.rb | 2 +- .../expression_factory_spec.rb | 4 - 9 files changed, 36 insertions(+), 179 deletions(-) diff --git a/cucumber-expressions/javascript/src/CucumberExpression.ts b/cucumber-expressions/javascript/src/CucumberExpression.ts index 4d36383ff0..0ed8de4122 100644 --- a/cucumber-expressions/javascript/src/CucumberExpression.ts +++ b/cucumber-expressions/javascript/src/CucumberExpression.ts @@ -137,7 +137,7 @@ export default class CucumberExpression implements Expression { (astNode) => NodeType.parameter == astNode.type ) if (parameterNodes.length > 0) { - throw createNodeContainedAParameterError(node) + throw createNodeContainedAParameterError(parameterNodes[0]) } } diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb index 76b312865e..91a13329b9 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb @@ -1,6 +1,7 @@ require 'cucumber/cucumber_expressions/argument' require 'cucumber/cucumber_expressions/tree_regexp' require 'cucumber/cucumber_expressions/errors' +require 'cucumber/cucumber_expressions/cucumber_expression_parser' module Cucumber module CucumberExpressions @@ -85,9 +86,9 @@ def rewriteAlternative(node) def rewriteParameter(node) name = node.text - # if (!ParameterType.isValidParameterTypeName(name)) { - # throw createInvalidParameterTypeName(name) - # } + unless ParameterType::is_valid_parameter_type_name(name) + raise InvalidParameterTypeName.new(node, @expression) + end parameterType = @parameter_type_registry.lookup_by_type_name(name) if parameterType.nil? @@ -116,7 +117,7 @@ def assertNotEmpty(node, createNodeWasNotEmptyException) def assertNoParameters(node, createNodeContainedAParameterError) parameterNodes = node.nodes.filter { |astNode| NodeType::Parameter == astNode.type } if parameterNodes.length > 0 - raise createNodeContainedAParameterError.call(node) + raise createNodeContainedAParameterError.call(parameterNodes[0]) end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index f1277d5bde..d7ac11d447 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -1,5 +1,6 @@ require 'cucumber/cucumber_expressions/ast' require 'cucumber/cucumber_expressions/errors' +require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer' module Cucumber diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb index 3313cab31c..f4ec37700d 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb @@ -36,7 +36,6 @@ def pointAtLocated(node) end pointer.join('') end - end class AlternativeMayNotExclusivelyContainOptionals < CucumberExpressionError @@ -127,6 +126,19 @@ def initialize(expression, beginToken, endToken, current) end end + + class InvalidParameterTypeName < CucumberExpressionError + def initialize(node, expression) + super(build_message( + node.start, + expression, + pointAtLocated(node), + "Parameter names may not contain '[]()$.|?*+'", + "Did you mean to use a regular expression?" + )) + end + end + class UndefinedParameterTypeError < CucumberExpressionError def initialize(node, expression, parameter_type_name) super(build_message(node.start, diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/parameter_type.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/parameter_type.rb index 26d4721747..420b81fae5 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/parameter_type.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/parameter_type.rb @@ -9,12 +9,16 @@ class ParameterType attr_reader :name, :type, :regexps def self.check_parameter_type_name(type_name) + unless is_valid_parameter_type_name(type_name) + raise CucumberExpressionError.new("Illegal character in parameter name {#{type_name}}. Parameter names may not contain '[]()$.|?*+'") + end + end + + def self.is_valid_parameter_type_name(type_name) unescaped_type_name = type_name.gsub(UNESCAPE_PATTERN) do $2 end - if ILLEGAL_PARAMETER_NAME_PATTERN =~ unescaped_type_name - raise CucumberExpressionError.new("Illegal character '#{$1}' in parameter name {#{unescaped_type_name}}") - end + !(ILLEGAL_PARAMETER_NAME_PATTERN =~ unescaped_type_name) end def prefer_for_regexp_match? @@ -58,16 +62,17 @@ def <=>(other) private + def string_array(regexps) array = regexps.is_a?(Array) ? regexps : [regexps] - array.map {|regexp| regexp.is_a?(String) ? regexp : regexp_source(regexp)} + array.map { |regexp| regexp.is_a?(String) ? regexp : regexp_source(regexp) } end def regexp_source(regexp) [ - 'EXTENDED', - 'IGNORECASE', - 'MULTILINE' + 'EXTENDED', + 'IGNORECASE', + 'MULTILINE' ].each do |option_name| option = Regexp.const_get(option_name) if regexp.options & option != 0 diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb index 75043434d5..e0b2ff3463 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb @@ -1,3 +1,5 @@ +require 'yaml' +require 'json' require 'cucumber/cucumber_expressions/cucumber_expression' require 'cucumber/cucumber_expressions/parameter_type_registry' @@ -22,166 +24,6 @@ module CucumberExpressions end end end - - it "documents match arguments" do - parameter_registry = ParameterTypeRegistry.new - - ### [capture-match-arguments] - expr = "I have {int} cuke(s)" - expression = CucumberExpression.new(expr, parameter_registry) - args = expression.match("I have 7 cukes") - expect(args[0].value(nil)).to eq(7) - ### [capture-match-arguments] - end - - it "matches float" do - expect(match("{float}", "")).to eq(nil) - expect(match("{float}", ".")).to eq(nil) - expect(match("{float}", ",")).to eq(nil) - expect(match("{float}", "-")).to eq(nil) - expect(match("{float}", "E")).to eq(nil) - expect(match("{float}", "1,")).to eq(nil) - expect(match("{float}", ",1")).to eq(nil) - expect(match("{float}", "1.")).to eq(nil) - - expect(match("{float}", "1")).to eq([1]) - expect(match("{float}", "-1")).to eq([-1]) - expect(match("{float}", "1.1")).to eq([1.1]) - expect(match("{float}", "1,000")).to eq(nil) - expect(match("{float}", "1,000,0")).to eq(nil) - expect(match("{float}", "1,000.1")).to eq(nil) - expect(match("{float}", "1,000,10")).to eq(nil) - expect(match("{float}", "1,0.1")).to eq(nil) - expect(match("{float}", "1,000,000.1")).to eq(nil) - expect(match("{float}", "-1.1")).to eq([-1.1]) - - expect(match("{float}", ".1")).to eq([0.1]) - expect(match("{float}", "-.1")).to eq([-0.1]) - expect(match("{float}", "-.1000001")).to eq([-0.1000001]) - expect(match("{float}", "1E1")).to eq([10.0]) - expect(match("{float}", ".1E1")).to eq([1]) - expect(match("{float}", "E1")).to eq(nil) - expect(match("{float}", "-.1E-1")).to eq([-0.01]) - expect(match("{float}", "-.1E-2")).to eq([-0.001]) - expect(match("{float}", "-.1E+1")).to eq([-1]) - expect(match("{float}", "-.1E+2")).to eq([-10]) - expect(match("{float}", "-.1E1")).to eq([-1]) - expect(match("{float}", "-.1E2")).to eq([-10]) - end - - - it "float with zero" do - expect(match("{float}", "0")).to eq([0.0]) - end - - it "matches anonymous" do - expect(match("{}", "0.22")).to eq(["0.22"]) - end - - it "exposes source" do - expr = "I have {int} cuke(s)" - expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr) - end - - it "unmatched optional groups have undefined values" do - parameter_type_registry = ParameterTypeRegistry.new - parameter_type_registry.define_parameter_type( - ParameterType.new( - 'textAndOrNumber', - /([A-Z]+)?(?: )?([0-9]+)?/, - Object, - -> (s1, s2) { - [s1, s2] - }, - false, - true - ) - ) - expression = CucumberExpression.new( - '{textAndOrNumber}', - parameter_type_registry - ) - - class World - end - - expect(expression.match("TLA")[0].value(World.new)).to eq(["TLA", nil]) - expect(expression.match("123")[0].value(World.new)).to eq([nil, "123"]) - end - - # Ruby specific - - it "delegates transform to custom object" do - parameter_type_registry = ParameterTypeRegistry.new - parameter_type_registry.define_parameter_type( - ParameterType.new( - 'widget', - /\w+/, - Object, - -> (s) { - self.create_widget(s) - }, - false, - true - ) - ) - expression = CucumberExpression.new( - 'I have a {widget}', - parameter_type_registry - ) - - class World - def create_widget(s) - "widget:#{s}" - end - end - - args = expression.match("I have a bolt") - expect(args[0].value(World.new)).to eq('widget:bolt') - end - - describe "escapes special characters" do - %w([ ] ^ $ . | ? * +).each do |character| - it "escapes #{character}" do - expr = "I have {int} cuke(s) and #{character}" - expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) - arg1 = expression.match("I have 800 cukes and #{character}")[0] - expect(arg1.value(nil)).to eq(800) - end - end - - it "escapes ." do - expr = "I have {int} cuke(s) and ." - expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) - expect(expression.match("I have 800 cukes and 3")).to eq(nil) - arg1 = expression.match("I have 800 cukes and .")[0] - expect(arg1.value(nil)).to eq(800) - end - - it "escapes \\" do - expr = "I have {int} cuke(s) and \\\\" - expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) - expect(expression.match("I have 800 cukes and 3")).to eq(nil) - arg1 = expression.match("I have 800 cukes and \\")[0] - expect(arg1.value(nil)).to eq(800) - end - - it "escapes |" do - expr = "I have {int} cuke(s) and a|b" - expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) - expect(expression.match("I have 800 cukes and a")).to eq(nil) - expect(expression.match("I have 800 cukes and b")).to eq(nil) - arg1 = expression.match("I have 800 cukes and a|b")[0] - expect(arg1.value(nil)).to eq(800) - end - end - - def match(expression, text) - cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new) - args = cucumber_expression.match(text) - return nil if args.nil? - args.map { |arg| arg.value(nil) } - end end end end diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb index 29f1780406..847155e465 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb @@ -69,7 +69,7 @@ def ==(other) true, false ) - end.to raise_error("Illegal character '[' in parameter name {[string]}") + end.to raise_error("Illegal character in parameter name {[string]}. Parameter names may not contain '[]()$.|?*+'") end describe CucumberExpression do diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/expression_examples_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/expression_examples_spec.rb index cc63e0942e..adaf020fa0 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/expression_examples_spec.rb @@ -7,7 +7,7 @@ module Cucumber module CucumberExpressions describe 'examples.txt' do def match(expression_text, text) - expression = expression_text =~ /\/(.*)\// ? + expression = expression_text =~ /^\/(.*)\/$/ ? RegularExpression.new(Regexp.new($1), ParameterTypeRegistry.new) : CucumberExpression.new(expression_text, ParameterTypeRegistry.new) diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/expression_factory_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/expression_factory_spec.rb index e33188d2e9..167040cb4e 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/expression_factory_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/expression_factory_spec.rb @@ -14,10 +14,6 @@ module CucumberExpressions it 'creates a CucumberExpression' do expect(@expression_factory.create_expression('{int}').class).to eq(CucumberExpression) end - - it 'creates a XXXRegularExpression' do - expect {@expression_factory.create_expression('hello {x}')}.to raise_error("Undefined parameter type {x}") - end end end end From 812255a890e964d980549abc6749de0a2e07ddaf Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 21 Nov 2020 21:40:59 +0100 Subject: [PATCH 171/183] Fix typescript again? --- .../javascript/src/CucumberExpression.ts | 2 +- cucumber-expressions/javascript/src/Errors.ts | 12 ++++++++---- cucumber-expressions/javascript/src/ParameterType.ts | 10 +++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/cucumber-expressions/javascript/src/CucumberExpression.ts b/cucumber-expressions/javascript/src/CucumberExpression.ts index 0ed8de4122..02fc4a2b3b 100644 --- a/cucumber-expressions/javascript/src/CucumberExpression.ts +++ b/cucumber-expressions/javascript/src/CucumberExpression.ts @@ -94,7 +94,7 @@ export default class CucumberExpression implements Expression { private rewriteParameter(node: Node) { const name = node.text() if (!ParameterType.isValidParameterTypeName(name)) { - throw createInvalidParameterTypeName(name) + throw createInvalidParameterTypeName(node, this.expression) } const parameterType = this.parameterTypeRegistry.lookupByTypeName(name) diff --git a/cucumber-expressions/javascript/src/Errors.ts b/cucumber-expressions/javascript/src/Errors.ts index 75cdd3aa49..1e72f2b414 100644 --- a/cucumber-expressions/javascript/src/Errors.ts +++ b/cucumber-expressions/javascript/src/Errors.ts @@ -108,11 +108,15 @@ export function createCantEscaped(expression: string, index: number) { ) } -export function createInvalidParameterTypeName(name: string) { +export function createInvalidParameterTypeName(node: Node, expression: string) { return new CucumberExpressionError( - 'Illegal character in parameter name {' + - name + - "}. Parameter names may not contain '[]()$.|?*+'" + message( + node.start, + expression, + pointAtLocated(node), + "Parameter names may not contain '[]()$.|?*+'", + 'Did you mean to use a regular expression?' + ) ) } diff --git a/cucumber-expressions/javascript/src/ParameterType.ts b/cucumber-expressions/javascript/src/ParameterType.ts index 2e36a240c9..b165a694d0 100644 --- a/cucumber-expressions/javascript/src/ParameterType.ts +++ b/cucumber-expressions/javascript/src/ParameterType.ts @@ -1,7 +1,4 @@ -import { - createInvalidParameterTypeName, - CucumberExpressionError, -} from './Errors' +import { CucumberExpressionError } from './Errors' const ILLEGAL_PARAMETER_NAME_PATTERN = /([[\]()$.|?*+])/ const UNESCAPE_PATTERN = () => /(\\([[$.|?*+\]]))/g @@ -21,7 +18,9 @@ export default class ParameterType { public static checkParameterTypeName(typeName: string) { if (!this.isValidParameterTypeName(typeName)) { - throw createInvalidParameterTypeName(typeName) + throw new CucumberExpressionError( + `Illegal character in parameter name {${typeName}}. Parameter names may not contain '[]()$.|?*+'` + ) } } @@ -75,6 +74,7 @@ function stringArray( regexps: ReadonlyArray | ReadonlyArray | RegExp | string ): string[] { const array = Array.isArray(regexps) ? regexps : [regexps] + // @ts-ignore this is actually correct return array.map((r: RegExp | string) => r instanceof RegExp ? regexpSource(r) : r ) From 5a5d3b7c0ba0297c4de3d2fc45e7711e56fd6155 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 21 Nov 2020 21:56:21 +0100 Subject: [PATCH 172/183] Fix naming conventions --- .../cucumber_expression.rb | 66 +++++------ .../cucumber_expression_parser.rb | 110 +++++++++--------- .../cucumber_expression_tokenizer.rb | 70 ++++++----- 3 files changed, 121 insertions(+), 125 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb index 91a13329b9..a60fffef6a 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb @@ -15,7 +15,7 @@ def initialize(expression, parameter_type_registry) @parameter_type_registry = parameter_type_registry parser = CucumberExpressionParser.new ast = parser.parse(expression) - pattern = rewriteToRegex(ast) + pattern = rewrite_to_regex(ast) @tree_regexp = TreeRegexp.new(pattern) end @@ -37,87 +37,87 @@ def to_s private - def rewriteToRegex(node) + def rewrite_to_regex(node) case node.type when NodeType::Text - return escapeRegex(node.text) + return escape_regex(node.text) when NodeType::Optional - return rewriteOptional(node) + return rewrite_optional(node) when NodeType::Alternation - return rewriteAlternation(node) + return rewrite_alternation(node) when NodeType::Alternative - return rewriteAlternative(node) + return rewrite_alternative(node) when NodeType::Parameter - return rewriteParameter(node) + return rewrite_parameter(node) when NodeType::Expression - return rewriteExpression(node) + return rewrite_expression(node) else # Can't happen as long as the switch case is exhaustive raise "#{node.type}" end end - def escapeRegex(expression) + def escape_regex(expression) expression.gsub(ESCAPE_PATTERN, '\\\\\1') end - def rewriteOptional(node) - assertNoParameters(node, lambda { |astNode| ParameterIsNotAllowedInOptional.new(astNode, @expression) }) - assertNotEmpty(node, lambda { |astNode| OptionalMayNotBeEmpty.new(astNode, @expression) }) - regex = node.nodes.map { |n| rewriteToRegex(n) }.join('') + def rewrite_optional(node) + assert_no_parameters(node, lambda { |astNode| ParameterIsNotAllowedInOptional.new(astNode, @expression) }) + assert_not_empty(node, lambda { |astNode| OptionalMayNotBeEmpty.new(astNode, @expression) }) + regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('') "(?:#{regex})?" end - def rewriteAlternation(node) + def rewrite_alternation(node) # Make sure the alternative parts aren't empty and don't contain parameter types node.nodes.each { |alternative| if alternative.nodes.length == 0 raise AlternativeMayNotBeEmpty.new(alternative, @expression) end - assertNotEmpty(alternative, lambda {|astNode| AlternativeMayNotExclusivelyContainOptionals.new(astNode, @expression)}) + assert_not_empty(alternative, lambda {|astNode| AlternativeMayNotExclusivelyContainOptionals.new(astNode, @expression)}) } - regex = node.nodes.map { |n| rewriteToRegex(n) }.join('|') + regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('|') "(?:#{regex})" end - def rewriteAlternative(node) - node.nodes.map { |lastNode| rewriteToRegex(lastNode) }.join('') + def rewrite_alternative(node) + node.nodes.map { |lastNode| rewrite_to_regex(lastNode) }.join('') end - def rewriteParameter(node) + def rewrite_parameter(node) name = node.text unless ParameterType::is_valid_parameter_type_name(name) raise InvalidParameterTypeName.new(node, @expression) end - parameterType = @parameter_type_registry.lookup_by_type_name(name) - if parameterType.nil? + parameter_type = @parameter_type_registry.lookup_by_type_name(name) + if parameter_type.nil? raise UndefinedParameterTypeError.new(node, @expression, name) end - @parameter_types.push(parameterType) - regexps = parameterType.regexps + @parameter_types.push(parameter_type) + regexps = parameter_type.regexps if regexps.length == 1 return "(#{regexps[0]})" end "((?:#{regexps.join(')|(?:')}))" end - def rewriteExpression(node) - regex = node.nodes.map { |n| rewriteToRegex(n) }.join('') + def rewrite_expression(node) + regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('') "^#{regex}$" end - def assertNotEmpty(node, createNodeWasNotEmptyException) - textNodes = node.nodes.filter { |astNode| NodeType::Text == astNode.type } - if textNodes.length == 0 - raise createNodeWasNotEmptyException.call(node) + def assert_not_empty(node, create_node_was_not_empty_error) + text_nodes = node.nodes.filter { |astNode| NodeType::Text == astNode.type } + if text_nodes.length == 0 + raise create_node_was_not_empty_error.call(node) end end - def assertNoParameters(node, createNodeContainedAParameterError) - parameterNodes = node.nodes.filter { |astNode| NodeType::Parameter == astNode.type } - if parameterNodes.length > 0 - raise createNodeContainedAParameterError.call(parameterNodes[0]) + def assert_no_parameters(node, create_node_contained_a_parameter_error) + parameter_nodes = node.nodes.filter { |astNode| NodeType::Parameter == astNode.type } + if parameter_nodes.length > 0 + raise create_node_contained_a_parameter_error.call(parameter_nodes[0]) end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index d7ac11d447..ebecc98bea 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -8,109 +8,109 @@ module CucumberExpressions class CucumberExpressionParser def parse(expression) # text := token - parseText = lambda do |expression, tokens, current| + parse_text = lambda do |expression, tokens, current| token = tokens[current] return 1, [Node.new(NodeType::Text, nil, token.text, token.start, token.end)] end # parameter := '{' + text* + '}' - parseParameter = parseBetween( + parse_parameter = parse_between( NodeType::Parameter, TokenType::BeginParameter, TokenType::EndParameter, - [parseText] + [parse_text] ) # optional := '(' + option* + ')' # option := parameter | text - parseOptional = parseBetween( + parse_optional = parse_between( NodeType::Optional, TokenType::BeginOptional, TokenType::EndOptional, - [parseParameter, parseText] + [parse_parameter, parse_text] ) # alternation := alternative* + ( '/' + alternative* )+ - parseAlternativeSeparator = lambda do |expression, tokens, current| - unless lookingAt(tokens, current, TokenType::Alternation) + parse_alternative_separator = lambda do |expression, tokens, current| + unless looking_at(tokens, current, TokenType::Alternation) return 0, nil end token = tokens[current] return 1, [Node.new(NodeType::Alternative, nil, token.text, token.start, token.end)] end - alternativeParsers = [ - parseAlternativeSeparator, - parseOptional, - parseParameter, - parseText, + alternative_parsers = [ + parse_alternative_separator, + parse_optional, + parse_parameter, + parse_text, ] # alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary) # left-boundary := whitespace | } | ^ # right-boundary := whitespace | { | $ # alternative: = optional | parameter | text - parseAlternation = lambda do |expression, tokens, current| + parse_alternation = lambda do |expression, tokens, current| previous = current - 1 - unless lookingAtAny(tokens, previous, [TokenType::StartOfLine, TokenType::WhiteSpace, TokenType::EndParameter]) + unless looking_at_any(tokens, previous, [TokenType::StartOfLine, TokenType::WhiteSpace, TokenType::EndParameter]) return 0, nil end - consumed, ast = parseTokensUntil(expression, alternativeParsers, tokens, current, [TokenType::WhiteSpace, TokenType::EndOfLine, TokenType::BeginParameter]) - subCurrent = current + consumed + consumed, ast = parse_tokens_until(expression, alternative_parsers, tokens, current, [TokenType::WhiteSpace, TokenType::EndOfLine, TokenType::BeginParameter]) + sub_current = current + consumed unless ast.map { |astNode| astNode.type }.include? NodeType::Alternative return 0, nil end start = tokens[current].start - _end = tokens[subCurrent].start + _end = tokens[sub_current].start # Does not consume right hand boundary token - return consumed, [Node.new(NodeType::Alternation, splitAlternatives(start, _end, ast), nil, start, _end)] + return consumed, [Node.new(NodeType::Alternation, split_alternatives(start, _end, ast), nil, start, _end)] end # # cucumber-expression := ( alternation | optional | parameter | text )* # - parseCucumberExpression = parseBetween( + parse_cucumber_expression = parse_between( NodeType::Expression, TokenType::StartOfLine, TokenType::EndOfLine, - [parseAlternation, parseOptional, parseParameter, parseText] + [parse_alternation, parse_optional, parse_parameter, parse_text] ) tokenizer = CucumberExpressionTokenizer.new tokens = tokenizer.tokenize(expression) - _, ast = parseCucumberExpression.call(expression, tokens, 0) + _, ast = parse_cucumber_expression.call(expression, tokens, 0) ast[0] end private - def parseBetween(type, beginToken, endToken, parsers) + def parse_between(type, begin_token, end_token, parsers) lambda do |expression, tokens, current| - unless lookingAt(tokens, current, beginToken) + unless looking_at(tokens, current, begin_token) return 0, nil end - subCurrent = current + 1 - consumed, ast = parseTokensUntil(expression, parsers, tokens, subCurrent, [endToken]) - subCurrent += consumed + sub_current = current + 1 + consumed, ast = parse_tokens_until(expression, parsers, tokens, sub_current, [end_token]) + sub_current += consumed # endToken not found - unless lookingAt(tokens, subCurrent, endToken) - raise MissingEndToken.new(expression, beginToken, endToken, tokens[current]) + unless looking_at(tokens, sub_current, end_token) + raise MissingEndToken.new(expression, begin_token, end_token, tokens[current]) end # consumes endToken start = tokens[current].start - _end = tokens[subCurrent].end - consumed = subCurrent + 1 - current + _end = tokens[sub_current].end + consumed = sub_current + 1 - current ast = [Node.new(type, ast, nil, start, _end)] return consumed, ast end end - def parseToken(expression, parsers, tokens, startAt) + def parse_token(expression, parsers, tokens, start_at) for parser in parsers do - consumed, ast = parser.call(expression, tokens, startAt) + consumed, ast = parser.call(expression, tokens, start_at) unless consumed == 0 return consumed, ast end @@ -119,36 +119,36 @@ def parseToken(expression, parsers, tokens, startAt) raise 'No eligible parsers for ' + tokens end - def parseTokensUntil(expression, parsers, tokens, startAt, endTokens) - current = startAt + def parse_tokens_until(expression, parsers, tokens, start_at, end_tokens) + current = start_at size = tokens.length ast = [] while current < size do - if lookingAtAny(tokens, current, endTokens) + if looking_at_any(tokens, current, end_tokens) break end - consumed, subAst = parseToken(expression, parsers, tokens, current) + consumed, sub_ast = parse_token(expression, parsers, tokens, current) if consumed == 0 # If configured correctly this will never happen # Keep to avoid infinite loops raise 'No eligible parsers for ' + tokens end current += consumed - ast += subAst + ast += sub_ast end - return current - startAt, ast + [current - start_at, ast] end - def lookingAtAny(tokens, at, tokenTypes) - for tokenType in tokenTypes - if lookingAt(tokens, at, tokenType) + def looking_at_any(tokens, at, token_types) + for token_type in token_types + if looking_at(tokens, at, token_type) return true end end - return false + false end - def lookingAt(tokens, at, token) + def looking_at(tokens, at, token) if at < 0 # If configured correctly this will never happen # Keep for completeness @@ -157,10 +157,10 @@ def lookingAt(tokens, at, token) if at >= tokens.length return token == TokenType::EndOfLine end - return tokens[at].type == token + tokens[at].type == token end - def splitAlternatives(start, _end, alternation) + def split_alternatives(start, _end, alternation) separators = [] alternatives = [] alternative = [] @@ -174,25 +174,25 @@ def splitAlternatives(start, _end, alternation) end } alternatives.push(alternative) - return createAlternativeNodes(start, _end, separators, alternatives) + create_alternative_nodes(start, _end, separators, alternatives) end - def createAlternativeNodes(start, _end, separators, alternatives) + def create_alternative_nodes(start, _end, separators, alternatives) nodes = [] alternatives.each_with_index do |n, i| if i == 0 - rightSeparator = separators[i] - nodes.push(Node.new(NodeType::Alternative, n, nil, start, rightSeparator.start)) + right_separator = separators[i] + nodes.push(Node.new(NodeType::Alternative, n, nil, start, right_separator.start)) elsif i == alternatives.length - 1 - leftSeparator = separators[i - 1] - nodes.push(Node.new(NodeType::Alternative, n, nil, leftSeparator.end, _end)) + left_separator = separators[i - 1] + nodes.push(Node.new(NodeType::Alternative, n, nil, left_separator.end, _end)) else - leftSeparator = separators[i - 1] - rightSeparator = separators[i] - nodes.push(Node.new(NodeType::Alternative, n, nil, leftSeparator.end, rightSeparator.start)) + left_separator = separators[i - 1] + right_separator = separators[i] + nodes.push(Node.new(NodeType::Alternative, n, nil, left_separator.end, right_separator.start)) end end - return nodes + nodes end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb index 50e95fd50b..c892d47112 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb @@ -8,10 +8,10 @@ def tokenize(expression) @expression = expression tokens = [] @buffer = [] - previousTokenType = TokenType::StartOfLine - treatAsText = false + previous_token_type = TokenType::StartOfLine + treat_as_text = false @escaped = 0 - @bufferStartIndex = 0 + @buffer_start_index = 0 codepoints = expression.codepoints @@ -20,83 +20,79 @@ def tokenize(expression) end codepoints.each do |codepoint| - if !treatAsText && Token.isEscapeCharacter(codepoint) + if !treat_as_text && Token.isEscapeCharacter(codepoint) @escaped += 1 - treatAsText = true + treat_as_text = true next end - currentTokenType = tokenTypeOf(codepoint, treatAsText) - treatAsText = false + current_token_type = token_type_of(codepoint, treat_as_text) + treat_as_text = false - if shouldCreateNewToken(previousTokenType, currentTokenType) - token = convertBufferToToken(previousTokenType) - previousTokenType = currentTokenType + if should_create_new_token(previous_token_type, current_token_type) + token = convert_buffer_to_token(previous_token_type) + previous_token_type = current_token_type @buffer.push(codepoint) tokens.push(token) else - previousTokenType = currentTokenType + previous_token_type = current_token_type @buffer.push(codepoint) end end if @buffer.length > 0 - token = convertBufferToToken(previousTokenType) + token = convert_buffer_to_token(previous_token_type) tokens.push(token) end - if (treatAsText) + if treat_as_text raise TheEndOfLineCannotBeEscaped.new(expression) end tokens.push( - Token.new(TokenType::EndOfLine, '', codepoints.length, codepoints.length) + Token.new(TokenType::EndOfLine, '', codepoints.length, codepoints.length) ) tokens end private + # TODO: Make these lambdas - def convertBufferToToken(tokenType) - escapeTokens = 0 - if (tokenType == TokenType::Text) - escapeTokens = @escaped + def convert_buffer_to_token(token_type) + escape_tokens = 0 + if token_type == TokenType::Text + escape_tokens = @escaped @escaped = 0 end - consumedIndex = @bufferStartIndex + @buffer.length + escapeTokens + consumed_index = @buffer_start_index + @buffer.length + escape_tokens t = Token.new( - tokenType, - @buffer.map{|codepoint| codepoint.chr(Encoding::UTF_8)}.join(''), - @bufferStartIndex, - consumedIndex + token_type, + @buffer.map { |codepoint| codepoint.chr(Encoding::UTF_8) }.join(''), + @buffer_start_index, + consumed_index ) @buffer = [] - @bufferStartIndex = consumedIndex - return t + @buffer_start_index = consumed_index + t end - def tokenTypeOf(codepoint, treatAsText) - if !treatAsText + def token_type_of(codepoint, treat_as_text) + unless treat_as_text return Token.typeOf(codepoint) end if Token.canEscape(codepoint) return TokenType::Text end raise CantEscape.new( - @expression, - @bufferStartIndex + @buffer.length + @escaped + @expression, + @buffer_start_index + @buffer.length + @escaped ) end - def shouldCreateNewToken(previousTokenType, currentTokenType) - if (currentTokenType != previousTokenType) - return true - end - return ( - currentTokenType != TokenType::WhiteSpace && - currentTokenType != TokenType::Text - ) + def should_create_new_token(previous_token_type, current_token_type) + current_token_type != previous_token_type || + (current_token_type != TokenType::WhiteSpace && current_token_type != TokenType::Text) end end end From dd49b5fc05665129845a69ca91d4e4b4acefcdba Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 22 Nov 2020 14:37:09 +0100 Subject: [PATCH 173/183] Extract regex tests --- .../go/cucumber_expression_regexp_test.go | 71 ---------- .../go/cucumber_expression_test.go | 79 +++-------- .../regex/alternation-with-optional.yaml | 2 + .../go/testdata/regex/alternation.yaml | 2 + .../go/testdata/regex/empty.yaml | 2 + .../regex/escape-regex-characters.yaml | 2 + .../go/testdata/regex/optional.yaml | 2 + .../go/testdata/regex/parameter.yaml | 2 + .../go/testdata/regex/text.yaml | 2 + .../go/testdata/regex/unicode.yaml | 2 + .../CucumberExpressionPatternTest.java | 80 ----------- .../CucumberExpressionTest.java | 19 ++- .../regex/alternation-with-optional.yaml | 2 + .../java/testdata/regex/alternation.yaml | 2 + .../java/testdata/regex/empty.yaml | 2 + .../regex/escape-regex-characters.yaml | 2 + .../java/testdata/regex/optional.yaml | 2 + .../java/testdata/regex/parameter.yaml | 2 + .../java/testdata/regex/text.yaml | 2 + .../java/testdata/regex/unicode.yaml | 2 + .../test/CucumberExpressionRegExpTest.ts | 51 ------- .../javascript/test/CucumberExpressionTest.ts | 68 +++------ .../regex/alternation-with-optional.yaml | 2 + .../testdata/regex/alternation.yaml | 2 + .../javascript/testdata/regex/empty.yaml | 2 + .../regex/escape-regex-characters.yaml | 2 + .../javascript/testdata/regex/optional.yaml | 2 + .../javascript/testdata/regex/parameter.yaml | 2 + .../javascript/testdata/regex/text.yaml | 2 + .../javascript/testdata/regex/unicode.yaml | 2 + .../cucumber_expression_regexp_spec.rb | 57 -------- .../cucumber_expression_spec.rb | 132 ++++++++++++++++++ .../regex/alternation-with-optional.yaml | 2 + .../ruby/testdata/regex/alternation.yaml | 2 + .../ruby/testdata/regex/empty.yaml | 2 + .../regex/escape-regex-characters.yaml | 2 + .../ruby/testdata/regex/optional.yaml | 2 + .../ruby/testdata/regex/parameter.yaml | 2 + .../ruby/testdata/regex/text.yaml | 2 + .../ruby/testdata/regex/unicode.yaml | 2 + .../regex/alternation-with-optional.yaml | 2 + .../testdata/regex/alternation.yaml | 2 + .../testdata/regex/empty.yaml | 2 + .../regex/escape-regex-characters.yaml | 2 + .../testdata/regex/optional.yaml | 2 + .../testdata/regex/parameter.yaml | 2 + cucumber-expressions/testdata/regex/text.yaml | 2 + .../testdata/regex/unicode.yaml | 2 + 48 files changed, 268 insertions(+), 369 deletions(-) delete mode 100644 cucumber-expressions/go/cucumber_expression_regexp_test.go create mode 100644 cucumber-expressions/go/testdata/regex/alternation-with-optional.yaml create mode 100644 cucumber-expressions/go/testdata/regex/alternation.yaml create mode 100644 cucumber-expressions/go/testdata/regex/empty.yaml create mode 100644 cucumber-expressions/go/testdata/regex/escape-regex-characters.yaml create mode 100644 cucumber-expressions/go/testdata/regex/optional.yaml create mode 100644 cucumber-expressions/go/testdata/regex/parameter.yaml create mode 100644 cucumber-expressions/go/testdata/regex/text.yaml create mode 100644 cucumber-expressions/go/testdata/regex/unicode.yaml delete mode 100644 cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java create mode 100644 cucumber-expressions/java/testdata/regex/alternation-with-optional.yaml create mode 100644 cucumber-expressions/java/testdata/regex/alternation.yaml create mode 100644 cucumber-expressions/java/testdata/regex/empty.yaml create mode 100644 cucumber-expressions/java/testdata/regex/escape-regex-characters.yaml create mode 100644 cucumber-expressions/java/testdata/regex/optional.yaml create mode 100644 cucumber-expressions/java/testdata/regex/parameter.yaml create mode 100644 cucumber-expressions/java/testdata/regex/text.yaml create mode 100644 cucumber-expressions/java/testdata/regex/unicode.yaml delete mode 100644 cucumber-expressions/javascript/test/CucumberExpressionRegExpTest.ts create mode 100644 cucumber-expressions/javascript/testdata/regex/alternation-with-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/regex/alternation.yaml create mode 100644 cucumber-expressions/javascript/testdata/regex/empty.yaml create mode 100644 cucumber-expressions/javascript/testdata/regex/escape-regex-characters.yaml create mode 100644 cucumber-expressions/javascript/testdata/regex/optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/regex/parameter.yaml create mode 100644 cucumber-expressions/javascript/testdata/regex/text.yaml create mode 100644 cucumber-expressions/javascript/testdata/regex/unicode.yaml delete mode 100644 cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb create mode 100644 cucumber-expressions/ruby/testdata/regex/alternation-with-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/regex/alternation.yaml create mode 100644 cucumber-expressions/ruby/testdata/regex/empty.yaml create mode 100644 cucumber-expressions/ruby/testdata/regex/escape-regex-characters.yaml create mode 100644 cucumber-expressions/ruby/testdata/regex/optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/regex/parameter.yaml create mode 100644 cucumber-expressions/ruby/testdata/regex/text.yaml create mode 100644 cucumber-expressions/ruby/testdata/regex/unicode.yaml create mode 100644 cucumber-expressions/testdata/regex/alternation-with-optional.yaml create mode 100644 cucumber-expressions/testdata/regex/alternation.yaml create mode 100644 cucumber-expressions/testdata/regex/empty.yaml create mode 100644 cucumber-expressions/testdata/regex/escape-regex-characters.yaml create mode 100644 cucumber-expressions/testdata/regex/optional.yaml create mode 100644 cucumber-expressions/testdata/regex/parameter.yaml create mode 100644 cucumber-expressions/testdata/regex/text.yaml create mode 100644 cucumber-expressions/testdata/regex/unicode.yaml diff --git a/cucumber-expressions/go/cucumber_expression_regexp_test.go b/cucumber-expressions/go/cucumber_expression_regexp_test.go deleted file mode 100644 index c3aa09733a..0000000000 --- a/cucumber-expressions/go/cucumber_expression_regexp_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package cucumberexpressions - -import ( - "github.com/stretchr/testify/require" - "testing" -) - -func TestCucumberExpressionRegExpTranslation(t *testing.T) { - t.Run("translates no arguments", func(t *testing.T) { - assertRegexp( - t, - "I have 10 cukes in my belly now", - "^I have 10 cukes in my belly now$", - ) - }) - - t.Run("translates alternation", func(t *testing.T) { - assertRegexp( - t, - "I had/have a great/nice/charming friend", - "^I (?:had|have) a (?:great|nice|charming) friend$", - ) - }) - - t.Run("translates alternation with non-alpha", func(t *testing.T) { - assertRegexp( - t, - "I said Alpha1/Beta1", - "^I said (?:Alpha1|Beta1)$", - ) - }) - t.Run("translates alternation with optional words", func(t *testing.T) { - assertRegexp( - t, - "the (test )chat/(test )call/(test )email interactions are visible", - "^the (?:(?:test )?chat|(?:test )?call|(?:test )?email) interactions are visible$", - ) - }) - - t.Run("translates parameters", func(t *testing.T) { - assertRegexp( - t, - "I have {float} cukes at {int} o'clock", - `^I have ([-+]?\d*\.?\d+) cukes at ((?:-?\d+)|(?:\d+)) o'clock$`, - ) - }) - - t.Run("translates parenthesis to non-capturing optional capture group", func(t *testing.T) { - assertRegexp( - t, - "I have many big(ish) cukes", - `^I have many big(?:ish)? cukes$`, - ) - }) - - t.Run("translates parenthesis with alpha unicode", func(t *testing.T) { - assertRegexp( - t, - "Привет, Мир(ы)!", - `^Привет, Мир(?:ы)?!$`, - ) - }) - -} - -func assertRegexp(t *testing.T, expression string, expectedRegexp string) { - parameterTypeRegistry := NewParameterTypeRegistry() - generator, err := NewCucumberExpression(expression, parameterTypeRegistry) - require.NoError(t, err) - require.Equal(t, expectedRegexp, generator.Regexp().String()) -} diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 9820a78e6f..91984e6f87 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -63,6 +63,28 @@ func TestCucumberExpression(t *testing.T) { } }) } + + assertRegex := func(t *testing.T, expected string, expr string) { + parameterTypeRegistry := NewParameterTypeRegistry() + expression, err := NewCucumberExpression(expr, parameterTypeRegistry) + require.NoError(t, err) + require.Equal(t, expected, expression.Regexp().String()) + } + + directory = "testdata/regex/" + files, err = ioutil.ReadDir(directory) + require.NoError(t, err) + + for _, file := range files { + contents, err := ioutil.ReadFile(directory + file.Name()) + require.NoError(t, err) + t.Run(fmt.Sprintf("%s", file.Name()), func(t *testing.T) { + var expectation expectation + err = yaml.Unmarshal(contents, &expectation) + require.NoError(t, err) + assertRegex(t, expectation.Expected, expectation.Expression) + }) + } }) t.Run("documents expression generation", func(t *testing.T) { @@ -147,63 +169,6 @@ func TestCucumberExpression(t *testing.T) { require.Equal(t, expression.Source(), expr) }) - t.Run("escapes special characters", func(t *testing.T) { - for _, char := range []string{"[", "]", "^", "$", ".", "|", "?", "*", "+"} { - t.Run(fmt.Sprintf("escapes %s", char), func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression( - t, - fmt.Sprintf("I have {int} cuke(s) and %s", char), - fmt.Sprintf("I have 800 cukes and %s", char), - ), - []interface{}{800}, - ) - }) - t.Run("escapes \\", func(t *testing.T) { - require.Equal( - t, - MatchCucumberExpression( - t, - "I have {int} cuke(s) and \\\\", - "I have 800 cukes and \\", - ), - []interface{}{800}, - ) - }) - - } - - t.Run("escapes .", func(t *testing.T) { - expr := "I have {int} cuke(s) and ." - parameterTypeRegistry := NewParameterTypeRegistry() - expression, err := NewCucumberExpression(expr, parameterTypeRegistry) - require.NoError(t, err) - args, err := expression.Match("I have 800 cukes and 3") - require.NoError(t, err) - require.Nil(t, args) - args, err = expression.Match("I have 800 cukes and .") - require.NoError(t, err) - require.NotNil(t, args) - }) - - t.Run("escapes |", func(t *testing.T) { - expr := "I have {int} cuke(s) and a|b" - parameterTypeRegistry := NewParameterTypeRegistry() - expression, err := NewCucumberExpression(expr, parameterTypeRegistry) - require.NoError(t, err) - args, err := expression.Match("I have 800 cukes and a") - require.NoError(t, err) - require.Nil(t, args) - args, err = expression.Match("I have 800 cukes and b") - require.NoError(t, err) - require.Nil(t, args) - args, err = expression.Match("I have 800 cukes and a|b") - require.NoError(t, err) - require.NotNil(t, args) - }) - }) - t.Run("unmatched optional groups have nil values", func(t *testing.T) { parameterTypeRegistry := NewParameterTypeRegistry() colorParameterType, err := NewParameterType( diff --git a/cucumber-expressions/go/testdata/regex/alternation-with-optional.yaml b/cucumber-expressions/go/testdata/regex/alternation-with-optional.yaml new file mode 100644 index 0000000000..73787b2b0a --- /dev/null +++ b/cucumber-expressions/go/testdata/regex/alternation-with-optional.yaml @@ -0,0 +1,2 @@ +expression: "a/b(c)" +expected: ^(?:a|b(?:c)?)$ diff --git a/cucumber-expressions/go/testdata/regex/alternation.yaml b/cucumber-expressions/go/testdata/regex/alternation.yaml new file mode 100644 index 0000000000..1dc293fb62 --- /dev/null +++ b/cucumber-expressions/go/testdata/regex/alternation.yaml @@ -0,0 +1,2 @@ +expression: "a/b c/d/e" +expected: ^(?:a|b) (?:c|d|e)$ diff --git a/cucumber-expressions/go/testdata/regex/empty.yaml b/cucumber-expressions/go/testdata/regex/empty.yaml new file mode 100644 index 0000000000..bb9a81906c --- /dev/null +++ b/cucumber-expressions/go/testdata/regex/empty.yaml @@ -0,0 +1,2 @@ +expression: "" +expected: ^$ diff --git a/cucumber-expressions/go/testdata/regex/escape-regex-characters.yaml b/cucumber-expressions/go/testdata/regex/escape-regex-characters.yaml new file mode 100644 index 0000000000..c8ea8c549e --- /dev/null +++ b/cucumber-expressions/go/testdata/regex/escape-regex-characters.yaml @@ -0,0 +1,2 @@ +expression: '^$[]\(\){}\\.|?*+' +expected: ^\^\$\[\]\(\)(.*)\\\.\|\?\*\+$ diff --git a/cucumber-expressions/go/testdata/regex/optional.yaml b/cucumber-expressions/go/testdata/regex/optional.yaml new file mode 100644 index 0000000000..7d6d84cc14 --- /dev/null +++ b/cucumber-expressions/go/testdata/regex/optional.yaml @@ -0,0 +1,2 @@ +expression: "(a)" +expected: ^(?:a)?$ diff --git a/cucumber-expressions/go/testdata/regex/parameter.yaml b/cucumber-expressions/go/testdata/regex/parameter.yaml new file mode 100644 index 0000000000..f793b21c0f --- /dev/null +++ b/cucumber-expressions/go/testdata/regex/parameter.yaml @@ -0,0 +1,2 @@ +expression: "{int}" +expected: ^((?:-?\d+)|(?:\d+))$ diff --git a/cucumber-expressions/go/testdata/regex/text.yaml b/cucumber-expressions/go/testdata/regex/text.yaml new file mode 100644 index 0000000000..2af3e41664 --- /dev/null +++ b/cucumber-expressions/go/testdata/regex/text.yaml @@ -0,0 +1,2 @@ +expression: "a" +expected: ^a$ diff --git a/cucumber-expressions/go/testdata/regex/unicode.yaml b/cucumber-expressions/go/testdata/regex/unicode.yaml new file mode 100644 index 0000000000..f93fe35db1 --- /dev/null +++ b/cucumber-expressions/go/testdata/regex/unicode.yaml @@ -0,0 +1,2 @@ +expression: "Привет, Мир(ы)!" +expected: ^Привет, Мир(?:ы)?!$ diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java deleted file mode 100644 index d262014e48..0000000000 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionPatternTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.cucumber.cucumberexpressions; - -import org.junit.jupiter.api.Test; - -import java.util.Locale; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * This test verifies that the regular expression generated - * from the cucumber expression is as expected. - */ -public class CucumberExpressionPatternTest { - - @Test - public void translates_no_args() { - assertPattern( - "hello", - "^hello$" - ); - } - - @Test - public void translates_alternation() { - assertPattern( - "I had/have a great/nice/charming friend", - "^I (?:had|have) a (?:great|nice|charming) friend$" - ); - } - - @Test - public void translates_alternation_with_non_alpha() { - assertPattern( - "I said Alpha1/Beta1", - "^I said (?:Alpha1|Beta1)$" - ); - } - - @Test - public void translates_alternation_with_optional_words() { - assertPattern( - "the (test )chat/(test )call/(test )email interactions are visible", - "^the (?:(?:test )?chat|(?:test )?call|(?:test )?email) interactions are visible$" - ); - } - - @Test - public void translates_parameters() { - assertPattern( - "I have {float} cukes at {int} o'clock", - "^I have (" + - "(?=.*\\d.*)" + - "[-+]?" + - "(?:\\d+(?:[,]?\\d+)*)*" + - "(?:[.](?=\\d.*))?\\d*" + - "(?:\\d+[E]-?\\d+)?) cukes at ((?:-?\\d+)|(?:\\d+)) o'clock$" - ); - } - - @Test - public void translates_parenthesis_to_non_capturing_optional_capture_group() { - assertPattern( - "I have many big(ish) cukes", - "^I have many big(?:ish)? cukes$" - ); - } - - @Test - public void translates_parenthesis_with_alpha_unicode() { - assertPattern( - "Привет, Мир(ы)!", - "^Привет, Мир(?:ы)?!$"); - } - - private void assertPattern(String expr, String expectedRegexp) { - CucumberExpression cucumberExpression = new CucumberExpression(expr, new ParameterTypeRegistry(Locale.ENGLISH)); - assertEquals(expectedRegexp, cucumberExpression.getRegexp().pattern()); - } - -} diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index eeeee2655a..8ee94ffe7b 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -23,6 +23,7 @@ import static java.nio.file.Files.newDirectoryStream; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @@ -32,16 +33,23 @@ class CucumberExpressionTest { private final ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH); - private static List acceptance_tests_pass() throws IOException { + private static List expression_acceptance_tests_pass() throws IOException { List paths = new ArrayList<>(); newDirectoryStream(Paths.get("testdata", "expression")).forEach(paths::add); paths.sort(Comparator.naturalOrder()); return paths; } + private static List regex_acceptance_tests_pass() throws IOException { + List paths = new ArrayList<>(); + newDirectoryStream(Paths.get("testdata", "regex")).forEach(paths::add); + paths.sort(Comparator.naturalOrder()); + return paths; + } + @ParameterizedTest @MethodSource - void acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { + void expression_acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { if (expectation.getException() == null) { CucumberExpression expression = new CucumberExpression(expectation.getExpression(), parameterTypeRegistry); List> match = expression.match(expectation.getText()); @@ -65,6 +73,13 @@ void acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) Expect } } + @ParameterizedTest + @MethodSource + void regex_acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) Expectation expectation) { + CucumberExpression expression = new CucumberExpression(expectation.getExpression(), parameterTypeRegistry); + assertEquals(expectation.getExpected(), expression.getRegexp().pattern()); + } + // Misc tests @Test diff --git a/cucumber-expressions/java/testdata/regex/alternation-with-optional.yaml b/cucumber-expressions/java/testdata/regex/alternation-with-optional.yaml new file mode 100644 index 0000000000..73787b2b0a --- /dev/null +++ b/cucumber-expressions/java/testdata/regex/alternation-with-optional.yaml @@ -0,0 +1,2 @@ +expression: "a/b(c)" +expected: ^(?:a|b(?:c)?)$ diff --git a/cucumber-expressions/java/testdata/regex/alternation.yaml b/cucumber-expressions/java/testdata/regex/alternation.yaml new file mode 100644 index 0000000000..1dc293fb62 --- /dev/null +++ b/cucumber-expressions/java/testdata/regex/alternation.yaml @@ -0,0 +1,2 @@ +expression: "a/b c/d/e" +expected: ^(?:a|b) (?:c|d|e)$ diff --git a/cucumber-expressions/java/testdata/regex/empty.yaml b/cucumber-expressions/java/testdata/regex/empty.yaml new file mode 100644 index 0000000000..bb9a81906c --- /dev/null +++ b/cucumber-expressions/java/testdata/regex/empty.yaml @@ -0,0 +1,2 @@ +expression: "" +expected: ^$ diff --git a/cucumber-expressions/java/testdata/regex/escape-regex-characters.yaml b/cucumber-expressions/java/testdata/regex/escape-regex-characters.yaml new file mode 100644 index 0000000000..c8ea8c549e --- /dev/null +++ b/cucumber-expressions/java/testdata/regex/escape-regex-characters.yaml @@ -0,0 +1,2 @@ +expression: '^$[]\(\){}\\.|?*+' +expected: ^\^\$\[\]\(\)(.*)\\\.\|\?\*\+$ diff --git a/cucumber-expressions/java/testdata/regex/optional.yaml b/cucumber-expressions/java/testdata/regex/optional.yaml new file mode 100644 index 0000000000..7d6d84cc14 --- /dev/null +++ b/cucumber-expressions/java/testdata/regex/optional.yaml @@ -0,0 +1,2 @@ +expression: "(a)" +expected: ^(?:a)?$ diff --git a/cucumber-expressions/java/testdata/regex/parameter.yaml b/cucumber-expressions/java/testdata/regex/parameter.yaml new file mode 100644 index 0000000000..f793b21c0f --- /dev/null +++ b/cucumber-expressions/java/testdata/regex/parameter.yaml @@ -0,0 +1,2 @@ +expression: "{int}" +expected: ^((?:-?\d+)|(?:\d+))$ diff --git a/cucumber-expressions/java/testdata/regex/text.yaml b/cucumber-expressions/java/testdata/regex/text.yaml new file mode 100644 index 0000000000..2af3e41664 --- /dev/null +++ b/cucumber-expressions/java/testdata/regex/text.yaml @@ -0,0 +1,2 @@ +expression: "a" +expected: ^a$ diff --git a/cucumber-expressions/java/testdata/regex/unicode.yaml b/cucumber-expressions/java/testdata/regex/unicode.yaml new file mode 100644 index 0000000000..f93fe35db1 --- /dev/null +++ b/cucumber-expressions/java/testdata/regex/unicode.yaml @@ -0,0 +1,2 @@ +expression: "Привет, Мир(ы)!" +expected: ^Привет, Мир(?:ы)?!$ diff --git a/cucumber-expressions/javascript/test/CucumberExpressionRegExpTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionRegExpTest.ts deleted file mode 100644 index 3d001f304a..0000000000 --- a/cucumber-expressions/javascript/test/CucumberExpressionRegExpTest.ts +++ /dev/null @@ -1,51 +0,0 @@ -import assert from 'assert' -import CucumberExpression from '../src/CucumberExpression' -import ParameterTypeRegistry from '../src/ParameterTypeRegistry' - -describe('CucumberExpression', () => { - describe('RegExp translation', () => { - it('translates no arguments', () => { - assertRegexp( - 'I have 10 cukes in my belly now', - /^I have 10 cukes in my belly now$/ - ) - }) - - it('translates alternation', () => { - assertRegexp( - 'I had/have a great/nice/charming friend', - /^I (?:had|have) a (?:great|nice|charming) friend$/ - ) - }) - - it('translates alternation with non-alpha', () => { - assertRegexp('I said Alpha1/Beta1', /^I said (?:Alpha1|Beta1)$/) - }) - - it('translates parameters', () => { - assertRegexp( - "I have {float} cukes at {int} o'clock", - /^I have ((?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][+-]?\d+)?) cukes at ((?:-?\d+)|(?:\d+)) o'clock$/ - ) - }) - - it('translates parenthesis to non-capturing optional capture group', () => { - assertRegexp( - 'I have many big(ish) cukes', - /^I have many big(?:ish)? cukes$/ - ) - }) - - it('translates parenthesis with alpha unicode', () => { - assertRegexp('Привет, Мир(ы)!', /^Привет, Мир(?:ы)?!$/) - }) - }) -}) - -const assertRegexp = (expression: string, expectedRegexp: RegExp) => { - const cucumberExpression = new CucumberExpression( - expression, - new ParameterTypeRegistry() - ) - assert.deepStrictEqual(cucumberExpression.regexp, expectedRegexp) -} diff --git a/cucumber-expressions/javascript/test/CucumberExpressionTest.ts b/cucumber-expressions/javascript/test/CucumberExpressionTest.ts index 937185e65e..3afe31887f 100644 --- a/cucumber-expressions/javascript/test/CucumberExpressionTest.ts +++ b/cucumber-expressions/javascript/test/CucumberExpressionTest.ts @@ -17,7 +17,7 @@ interface Expectation { describe('CucumberExpression', () => { fs.readdirSync('testdata/expression').forEach((testcase) => { const testCaseData = fs.readFileSync( - `testdata/expression/matches-int.yaml`, + `testdata/expression/${testcase}`, 'utf-8' ) const expectation = yaml.safeLoad(testCaseData) as Expectation @@ -31,7 +31,9 @@ describe('CucumberExpression', () => { const matches = expression.match(expectation.text) assert.deepStrictEqual( JSON.parse( - JSON.stringify(matches.map((value) => value.getValue(null))) + JSON.stringify( + matches ? matches.map((value) => value.getValue(null)) : null + ) ), // Removes type information. JSON.parse(expectation.expected) ) @@ -47,6 +49,19 @@ describe('CucumberExpression', () => { }) }) + fs.readdirSync('testdata/regex').forEach((testcase) => { + const testCaseData = fs.readFileSync(`testdata/regex/${testcase}`, 'utf-8') + const expectation = yaml.safeLoad(testCaseData) as Expectation + it(`${testcase}`, () => { + const parameterTypeRegistry = new ParameterTypeRegistry() + const expression = new CucumberExpression( + expectation.expression, + parameterTypeRegistry + ) + assert.deepStrictEqual(expression.regexp.source, expectation.expected) + }) + }) + it('documents match arguments', () => { const parameterTypeRegistry = new ParameterTypeRegistry() @@ -166,55 +181,6 @@ describe('CucumberExpression', () => { const args = expression.match(`I have a bolt`) assert.strictEqual(args[0].getValue(world), 'widget:bolt') }) - - describe('escapes special characters', () => { - const special = ['[', ']', '^', '$', '.', '|', '?', '*', '+'] - special.forEach((character) => { - it(`escapes ${character}`, () => { - const expr = `I have {int} cuke(s) and ${character}` - const expression = new CucumberExpression( - expr, - new ParameterTypeRegistry() - ) - const arg1 = expression.match(`I have 800 cukes and ${character}`)[0] - assert.strictEqual(arg1.getValue(null), 800) - }) - }) - - it(`escapes .`, () => { - const expr = `I have {int} cuke(s) and .` - const expression = new CucumberExpression( - expr, - new ParameterTypeRegistry() - ) - assert.strictEqual(expression.match(`I have 800 cukes and 3`), null) - const arg1 = expression.match(`I have 800 cukes and .`)[0] - assert.strictEqual(arg1.getValue(null), 800) - }) - - it(`escapes \\`, () => { - const expr = `I have {int} cuke(s) and \\\\` - const expression = new CucumberExpression( - expr, - new ParameterTypeRegistry() - ) - assert.strictEqual(expression.match(`I have 800 cukes and 3`), null) - const arg1 = expression.match(`I have 800 cukes and \\`)[0] - assert.strictEqual(arg1.getValue(null), 800) - }) - - it(`escapes |`, () => { - const expr = `I have {int} cuke(s) and a|b` - const expression = new CucumberExpression( - expr, - new ParameterTypeRegistry() - ) - assert.strictEqual(expression.match(`I have 800 cukes and a`), null) - assert.strictEqual(expression.match(`I have 800 cukes and b`), null) - const arg1 = expression.match(`I have 800 cukes and a|b`)[0] - assert.strictEqual(arg1.getValue(null), 800) - }) - }) }) const match = (expression: string, text: string) => { diff --git a/cucumber-expressions/javascript/testdata/regex/alternation-with-optional.yaml b/cucumber-expressions/javascript/testdata/regex/alternation-with-optional.yaml new file mode 100644 index 0000000000..73787b2b0a --- /dev/null +++ b/cucumber-expressions/javascript/testdata/regex/alternation-with-optional.yaml @@ -0,0 +1,2 @@ +expression: "a/b(c)" +expected: ^(?:a|b(?:c)?)$ diff --git a/cucumber-expressions/javascript/testdata/regex/alternation.yaml b/cucumber-expressions/javascript/testdata/regex/alternation.yaml new file mode 100644 index 0000000000..1dc293fb62 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/regex/alternation.yaml @@ -0,0 +1,2 @@ +expression: "a/b c/d/e" +expected: ^(?:a|b) (?:c|d|e)$ diff --git a/cucumber-expressions/javascript/testdata/regex/empty.yaml b/cucumber-expressions/javascript/testdata/regex/empty.yaml new file mode 100644 index 0000000000..bb9a81906c --- /dev/null +++ b/cucumber-expressions/javascript/testdata/regex/empty.yaml @@ -0,0 +1,2 @@ +expression: "" +expected: ^$ diff --git a/cucumber-expressions/javascript/testdata/regex/escape-regex-characters.yaml b/cucumber-expressions/javascript/testdata/regex/escape-regex-characters.yaml new file mode 100644 index 0000000000..c8ea8c549e --- /dev/null +++ b/cucumber-expressions/javascript/testdata/regex/escape-regex-characters.yaml @@ -0,0 +1,2 @@ +expression: '^$[]\(\){}\\.|?*+' +expected: ^\^\$\[\]\(\)(.*)\\\.\|\?\*\+$ diff --git a/cucumber-expressions/javascript/testdata/regex/optional.yaml b/cucumber-expressions/javascript/testdata/regex/optional.yaml new file mode 100644 index 0000000000..7d6d84cc14 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/regex/optional.yaml @@ -0,0 +1,2 @@ +expression: "(a)" +expected: ^(?:a)?$ diff --git a/cucumber-expressions/javascript/testdata/regex/parameter.yaml b/cucumber-expressions/javascript/testdata/regex/parameter.yaml new file mode 100644 index 0000000000..f793b21c0f --- /dev/null +++ b/cucumber-expressions/javascript/testdata/regex/parameter.yaml @@ -0,0 +1,2 @@ +expression: "{int}" +expected: ^((?:-?\d+)|(?:\d+))$ diff --git a/cucumber-expressions/javascript/testdata/regex/text.yaml b/cucumber-expressions/javascript/testdata/regex/text.yaml new file mode 100644 index 0000000000..2af3e41664 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/regex/text.yaml @@ -0,0 +1,2 @@ +expression: "a" +expected: ^a$ diff --git a/cucumber-expressions/javascript/testdata/regex/unicode.yaml b/cucumber-expressions/javascript/testdata/regex/unicode.yaml new file mode 100644 index 0000000000..f93fe35db1 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/regex/unicode.yaml @@ -0,0 +1,2 @@ +expression: "Привет, Мир(ы)!" +expected: ^Привет, Мир(?:ы)?!$ diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb deleted file mode 100644 index 0e7ba95cb3..0000000000 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'cucumber/cucumber_expressions/cucumber_expression' -require 'cucumber/cucumber_expressions/parameter_type_registry' - -module Cucumber - module CucumberExpressions - describe CucumberExpression do - context "Regexp translation" do - def assert_regexp(expression, regexp) - cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new) - expect(regexp).to eq(cucumber_expression.regexp) - end - - it "translates no arguments" do - assert_regexp( - "I have 10 cukes in my belly now", - /^I have 10 cukes in my belly now$/ - ) - end - - it "translates alternation" do - assert_regexp( - "I had/have a great/nice/charming friend", - /^I (?:had|have) a (?:great|nice|charming) friend$/ - ) - end - - it "translates alternation with non-alpha" do - assert_regexp( - "I said Alpha1/Beta1", - /^I said (?:Alpha1|Beta1)$/ - ) - end - - it "translates parameters" do - assert_regexp( - "I have {float} cukes at {int} o'clock", - /^I have ((?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][-+]?\d+)?) cukes at ((?:-?\d+)|(?:\d+)) o'clock$/ - ) - end - - it "translates parenthesis to non-capturing optional capture group" do - assert_regexp( - "I have many big(ish) cukes", - /^I have many big(?:ish)? cukes$/ - ) - end - - it "translates parenthesis with alpha unicode" do - assert_regexp( - "Привет, Мир(ы)!", - /^Привет, Мир(?:ы)?!$/ - ) - end - end - end - end -end diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb index e0b2ff3463..e975bfa917 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb @@ -24,6 +24,138 @@ module CucumberExpressions end end end + + Dir['testdata/regex/*.yaml'].each do |testcase| + expectation = YAML.load_file(testcase) # encoding? + it "#{testcase}" do + parameter_registry = ParameterTypeRegistry.new + cucumber_expression = CucumberExpression.new(expectation['expression'], parameter_registry) + expect(cucumber_expression.regexp.source).to eq(expectation['expected']) + end + end + + it "documents match arguments" do + parameter_registry = ParameterTypeRegistry.new + + ### [capture-match-arguments] + expr = "I have {int} cuke(s)" + expression = CucumberExpression.new(expr, parameter_registry) + args = expression.match("I have 7 cukes") + expect(args[0].value(nil)).to eq(7) + ### [capture-match-arguments] + end + + it "matches float" do + expect(match("{float}", "")).to eq(nil) + expect(match("{float}", ".")).to eq(nil) + expect(match("{float}", ",")).to eq(nil) + expect(match("{float}", "-")).to eq(nil) + expect(match("{float}", "E")).to eq(nil) + expect(match("{float}", "1,")).to eq(nil) + expect(match("{float}", ",1")).to eq(nil) + expect(match("{float}", "1.")).to eq(nil) + + expect(match("{float}", "1")).to eq([1]) + expect(match("{float}", "-1")).to eq([-1]) + expect(match("{float}", "1.1")).to eq([1.1]) + expect(match("{float}", "1,000")).to eq(nil) + expect(match("{float}", "1,000,0")).to eq(nil) + expect(match("{float}", "1,000.1")).to eq(nil) + expect(match("{float}", "1,000,10")).to eq(nil) + expect(match("{float}", "1,0.1")).to eq(nil) + expect(match("{float}", "1,000,000.1")).to eq(nil) + expect(match("{float}", "-1.1")).to eq([-1.1]) + + expect(match("{float}", ".1")).to eq([0.1]) + expect(match("{float}", "-.1")).to eq([-0.1]) + expect(match("{float}", "-.1000001")).to eq([-0.1000001]) + expect(match("{float}", "1E1")).to eq([10.0]) + expect(match("{float}", ".1E1")).to eq([1]) + expect(match("{float}", "E1")).to eq(nil) + expect(match("{float}", "-.1E-1")).to eq([-0.01]) + expect(match("{float}", "-.1E-2")).to eq([-0.001]) + expect(match("{float}", "-.1E+1")).to eq([-1]) + expect(match("{float}", "-.1E+2")).to eq([-10]) + expect(match("{float}", "-.1E1")).to eq([-1]) + expect(match("{float}", "-.1E2")).to eq([-10]) + end + + it "float with zero" do + expect(match("{float}", "0")).to eq([0.0]) + end + + it "matches anonymous" do + expect(match("{}", "0.22")).to eq(["0.22"]) + end + + it "exposes source" do + expr = "I have {int} cuke(s)" + expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr) + end + + it "unmatched optional groups have undefined values" do + parameter_type_registry = ParameterTypeRegistry.new + parameter_type_registry.define_parameter_type( + ParameterType.new( + 'textAndOrNumber', + /([A-Z]+)?(?: )?([0-9]+)?/, + Object, + -> (s1, s2) { + [s1, s2] + }, + false, + true + ) + ) + expression = CucumberExpression.new( + '{textAndOrNumber}', + parameter_type_registry + ) + + class World + end + + expect(expression.match("TLA")[0].value(World.new)).to eq(["TLA", nil]) + expect(expression.match("123")[0].value(World.new)).to eq([nil, "123"]) + end + + # Ruby specific + + it "delegates transform to custom object" do + parameter_type_registry = ParameterTypeRegistry.new + parameter_type_registry.define_parameter_type( + ParameterType.new( + 'widget', + /\w+/, + Object, + -> (s) { + self.create_widget(s) + }, + false, + true + ) + ) + expression = CucumberExpression.new( + 'I have a {widget}', + parameter_type_registry + ) + + class World + def create_widget(s) + "widget:#{s}" + end + end + + args = expression.match("I have a bolt") + expect(args[0].value(World.new)).to eq('widget:bolt') + end + + def match(expression, text) + cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new) + args = cucumber_expression.match(text) + return nil if args.nil? + args.map { |arg| arg.value(nil) } + end end end end diff --git a/cucumber-expressions/ruby/testdata/regex/alternation-with-optional.yaml b/cucumber-expressions/ruby/testdata/regex/alternation-with-optional.yaml new file mode 100644 index 0000000000..73787b2b0a --- /dev/null +++ b/cucumber-expressions/ruby/testdata/regex/alternation-with-optional.yaml @@ -0,0 +1,2 @@ +expression: "a/b(c)" +expected: ^(?:a|b(?:c)?)$ diff --git a/cucumber-expressions/ruby/testdata/regex/alternation.yaml b/cucumber-expressions/ruby/testdata/regex/alternation.yaml new file mode 100644 index 0000000000..1dc293fb62 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/regex/alternation.yaml @@ -0,0 +1,2 @@ +expression: "a/b c/d/e" +expected: ^(?:a|b) (?:c|d|e)$ diff --git a/cucumber-expressions/ruby/testdata/regex/empty.yaml b/cucumber-expressions/ruby/testdata/regex/empty.yaml new file mode 100644 index 0000000000..bb9a81906c --- /dev/null +++ b/cucumber-expressions/ruby/testdata/regex/empty.yaml @@ -0,0 +1,2 @@ +expression: "" +expected: ^$ diff --git a/cucumber-expressions/ruby/testdata/regex/escape-regex-characters.yaml b/cucumber-expressions/ruby/testdata/regex/escape-regex-characters.yaml new file mode 100644 index 0000000000..c8ea8c549e --- /dev/null +++ b/cucumber-expressions/ruby/testdata/regex/escape-regex-characters.yaml @@ -0,0 +1,2 @@ +expression: '^$[]\(\){}\\.|?*+' +expected: ^\^\$\[\]\(\)(.*)\\\.\|\?\*\+$ diff --git a/cucumber-expressions/ruby/testdata/regex/optional.yaml b/cucumber-expressions/ruby/testdata/regex/optional.yaml new file mode 100644 index 0000000000..7d6d84cc14 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/regex/optional.yaml @@ -0,0 +1,2 @@ +expression: "(a)" +expected: ^(?:a)?$ diff --git a/cucumber-expressions/ruby/testdata/regex/parameter.yaml b/cucumber-expressions/ruby/testdata/regex/parameter.yaml new file mode 100644 index 0000000000..f793b21c0f --- /dev/null +++ b/cucumber-expressions/ruby/testdata/regex/parameter.yaml @@ -0,0 +1,2 @@ +expression: "{int}" +expected: ^((?:-?\d+)|(?:\d+))$ diff --git a/cucumber-expressions/ruby/testdata/regex/text.yaml b/cucumber-expressions/ruby/testdata/regex/text.yaml new file mode 100644 index 0000000000..2af3e41664 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/regex/text.yaml @@ -0,0 +1,2 @@ +expression: "a" +expected: ^a$ diff --git a/cucumber-expressions/ruby/testdata/regex/unicode.yaml b/cucumber-expressions/ruby/testdata/regex/unicode.yaml new file mode 100644 index 0000000000..f93fe35db1 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/regex/unicode.yaml @@ -0,0 +1,2 @@ +expression: "Привет, Мир(ы)!" +expected: ^Привет, Мир(?:ы)?!$ diff --git a/cucumber-expressions/testdata/regex/alternation-with-optional.yaml b/cucumber-expressions/testdata/regex/alternation-with-optional.yaml new file mode 100644 index 0000000000..73787b2b0a --- /dev/null +++ b/cucumber-expressions/testdata/regex/alternation-with-optional.yaml @@ -0,0 +1,2 @@ +expression: "a/b(c)" +expected: ^(?:a|b(?:c)?)$ diff --git a/cucumber-expressions/testdata/regex/alternation.yaml b/cucumber-expressions/testdata/regex/alternation.yaml new file mode 100644 index 0000000000..1dc293fb62 --- /dev/null +++ b/cucumber-expressions/testdata/regex/alternation.yaml @@ -0,0 +1,2 @@ +expression: "a/b c/d/e" +expected: ^(?:a|b) (?:c|d|e)$ diff --git a/cucumber-expressions/testdata/regex/empty.yaml b/cucumber-expressions/testdata/regex/empty.yaml new file mode 100644 index 0000000000..bb9a81906c --- /dev/null +++ b/cucumber-expressions/testdata/regex/empty.yaml @@ -0,0 +1,2 @@ +expression: "" +expected: ^$ diff --git a/cucumber-expressions/testdata/regex/escape-regex-characters.yaml b/cucumber-expressions/testdata/regex/escape-regex-characters.yaml new file mode 100644 index 0000000000..c8ea8c549e --- /dev/null +++ b/cucumber-expressions/testdata/regex/escape-regex-characters.yaml @@ -0,0 +1,2 @@ +expression: '^$[]\(\){}\\.|?*+' +expected: ^\^\$\[\]\(\)(.*)\\\.\|\?\*\+$ diff --git a/cucumber-expressions/testdata/regex/optional.yaml b/cucumber-expressions/testdata/regex/optional.yaml new file mode 100644 index 0000000000..7d6d84cc14 --- /dev/null +++ b/cucumber-expressions/testdata/regex/optional.yaml @@ -0,0 +1,2 @@ +expression: "(a)" +expected: ^(?:a)?$ diff --git a/cucumber-expressions/testdata/regex/parameter.yaml b/cucumber-expressions/testdata/regex/parameter.yaml new file mode 100644 index 0000000000..f793b21c0f --- /dev/null +++ b/cucumber-expressions/testdata/regex/parameter.yaml @@ -0,0 +1,2 @@ +expression: "{int}" +expected: ^((?:-?\d+)|(?:\d+))$ diff --git a/cucumber-expressions/testdata/regex/text.yaml b/cucumber-expressions/testdata/regex/text.yaml new file mode 100644 index 0000000000..2af3e41664 --- /dev/null +++ b/cucumber-expressions/testdata/regex/text.yaml @@ -0,0 +1,2 @@ +expression: "a" +expected: ^a$ diff --git a/cucumber-expressions/testdata/regex/unicode.yaml b/cucumber-expressions/testdata/regex/unicode.yaml new file mode 100644 index 0000000000..f93fe35db1 --- /dev/null +++ b/cucumber-expressions/testdata/regex/unicode.yaml @@ -0,0 +1,2 @@ +expression: "Привет, Мир(ы)!" +expected: ^Привет, Мир(?:ы)?!$ From 63462bccc911ed398f092dc8e921074bcb851777 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 22 Nov 2020 14:48:47 +0100 Subject: [PATCH 174/183] Ruby conventions --- .../lib/cucumber/cucumber_expressions/ast.rb | 122 +++++++++--------- .../cucumber_expression.rb | 16 +-- .../cucumber_expression_parser.rb | 48 +++---- .../cucumber_expression_tokenizer.rb | 18 +-- .../cucumber/cucumber_expressions/errors.rb | 36 +++--- 5 files changed, 120 insertions(+), 120 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb index 588367e84f..c096cc8718 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/ast.rb @@ -1,11 +1,11 @@ module Cucumber module CucumberExpressions - EscapeCharacter = '\\' - AlternationCharacter = '/' - BeginParameterCharacter = '{' - EndParameterCharacter = '}' - BeginOptionalCharacter = '(' - EndOptionalCharacter = ')' + ESCAPE_CHARACTER = '\\' + ALTERNATION_CHARACTER = '/' + BEGIN_PARAMETER_CHARACTER = '{' + END_PARAMETER_CHARACTER = '}' + BEGIN_OPTIONAL_CHARACTER = '(' + END_OPTIONAL_CHARACTER = ')' class Node def initialize(type, nodes, token, start, _end) @@ -43,7 +43,7 @@ def text if @token.nil? return @nodes.map { |value| value.text }.join('') end - return @token + @token end def to_hash @@ -62,12 +62,12 @@ def to_hash end module NodeType - Text = 'TEXT_NODE' - Optional = 'OPTIONAL_NODE' - Alternation = 'ALTERNATION_NODE' - Alternative = 'ALTERNATIVE_NODE' - Parameter = 'PARAMETER_NODE' - Expression = 'EXPRESSION_NODE' + TEXT = 'TEXT_NODE' + OPTIONAL = 'OPTIONAL_NODE' + ALTERNATION = 'ALTERNATION_NODE' + ALTERNATIVE = 'ALTERNATIVE_NODE' + PARAMETER = 'PARAMETER_NODE' + EXPRESSION = 'EXPRESSION_NODE' end @@ -92,84 +92,84 @@ def end @end end - def self.isEscapeCharacter(codepoint) - codepoint.chr(Encoding::UTF_8) == EscapeCharacter + def self.is_escape_character(codepoint) + codepoint.chr(Encoding::UTF_8) == ESCAPE_CHARACTER end - def self.canEscape(codepoint) + def self.can_escape(codepoint) c = codepoint.chr(Encoding::UTF_8) if c == ' ' # TODO: Unicode whitespace? return true end case c - when EscapeCharacter + when ESCAPE_CHARACTER true - when AlternationCharacter + when ALTERNATION_CHARACTER true - when BeginParameterCharacter + when BEGIN_PARAMETER_CHARACTER true - when EndParameterCharacter + when END_PARAMETER_CHARACTER true - when BeginOptionalCharacter + when BEGIN_OPTIONAL_CHARACTER true - when EndOptionalCharacter + when END_OPTIONAL_CHARACTER true else false end end - def self.typeOf(codepoint) + def self.type_of(codepoint) c = codepoint.chr(Encoding::UTF_8) if c == ' ' # TODO: Unicode whitespace? - return TokenType::WhiteSpace + return TokenType::WHITE_SPACE end case c - when AlternationCharacter - TokenType::Alternation - when BeginParameterCharacter - TokenType::BeginParameter - when EndParameterCharacter - TokenType::EndParameter - when BeginOptionalCharacter - TokenType::BeginOptional - when EndOptionalCharacter - TokenType::EndOptional + when ALTERNATION_CHARACTER + TokenType::ALTERNATION + when BEGIN_PARAMETER_CHARACTER + TokenType::BEGIN_PARAMETER + when END_PARAMETER_CHARACTER + TokenType::END_PARAMETER + when BEGIN_OPTIONAL_CHARACTER + TokenType::BEGIN_OPTIONAL + when END_OPTIONAL_CHARACTER + TokenType::END_OPTIONAL else - TokenType::Text + TokenType::TEXT end end - def self.symbolOf(token) + def self.symbol_of(token) case token - when TokenType::BeginOptional - return BeginOptionalCharacter - when TokenType::EndOptional - return EndOptionalCharacter - when TokenType::BeginParameter - return BeginParameterCharacter - when TokenType::EndParameter - return EndParameterCharacter - when TokenType::Alternation - return AlternationCharacter + when TokenType::BEGIN_OPTIONAL + return BEGIN_OPTIONAL_CHARACTER + when TokenType::END_OPTIONAL + return END_OPTIONAL_CHARACTER + when TokenType::BEGIN_PARAMETER + return BEGIN_PARAMETER_CHARACTER + when TokenType::END_PARAMETER + return END_PARAMETER_CHARACTER + when TokenType::ALTERNATION + return ALTERNATION_CHARACTER else return '' end end - def self.purposeOf(token) + def self.purpose_of(token) case token - when TokenType::BeginOptional + when TokenType::BEGIN_OPTIONAL return 'optional text' - when TokenType::EndOptional + when TokenType::END_OPTIONAL return 'optional text' - when TokenType::BeginParameter + when TokenType::BEGIN_PARAMETER return 'a parameter' - when TokenType::EndParameter + when TokenType::END_PARAMETER return 'a parameter' - when TokenType::Alternation + when TokenType::ALTERNATION return 'alternation' else return '' @@ -187,15 +187,15 @@ def to_hash end module TokenType - StartOfLine = 'START_OF_LINE' - EndOfLine = 'END_OF_LINE' - WhiteSpace = 'WHITE_SPACE' - BeginOptional = 'BEGIN_OPTIONAL' - EndOptional = 'END_OPTIONAL' - BeginParameter = 'BEGIN_PARAMETER' - EndParameter = 'END_PARAMETER' - Alternation = 'ALTERNATION' - Text = 'TEXT' + START_OF_LINE = 'START_OF_LINE' + END_OF_LINE = 'END_OF_LINE' + WHITE_SPACE = 'WHITE_SPACE' + BEGIN_OPTIONAL = 'BEGIN_OPTIONAL' + END_OPTIONAL = 'END_OPTIONAL' + BEGIN_PARAMETER = 'BEGIN_PARAMETER' + END_PARAMETER = 'END_PARAMETER' + ALTERNATION = 'ALTERNATION' + TEXT = 'TEXT' end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb index a60fffef6a..8a0ba7b822 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb @@ -39,17 +39,17 @@ def to_s def rewrite_to_regex(node) case node.type - when NodeType::Text + when NodeType::TEXT return escape_regex(node.text) - when NodeType::Optional + when NodeType::OPTIONAL return rewrite_optional(node) - when NodeType::Alternation + when NodeType::ALTERNATION return rewrite_alternation(node) - when NodeType::Alternative + when NodeType::ALTERNATIVE return rewrite_alternative(node) - when NodeType::Parameter + when NodeType::PARAMETER return rewrite_parameter(node) - when NodeType::Expression + when NodeType::EXPRESSION return rewrite_expression(node) else # Can't happen as long as the switch case is exhaustive @@ -108,14 +108,14 @@ def rewrite_expression(node) end def assert_not_empty(node, create_node_was_not_empty_error) - text_nodes = node.nodes.filter { |astNode| NodeType::Text == astNode.type } + text_nodes = node.nodes.filter { |astNode| NodeType::TEXT == astNode.type } if text_nodes.length == 0 raise create_node_was_not_empty_error.call(node) end end def assert_no_parameters(node, create_node_contained_a_parameter_error) - parameter_nodes = node.nodes.filter { |astNode| NodeType::Parameter == astNode.type } + parameter_nodes = node.nodes.filter { |astNode| NodeType::PARAMETER == astNode.type } if parameter_nodes.length > 0 raise create_node_contained_a_parameter_error.call(parameter_nodes[0]) end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index ebecc98bea..1d4a8d2056 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -8,35 +8,35 @@ module CucumberExpressions class CucumberExpressionParser def parse(expression) # text := token - parse_text = lambda do |expression, tokens, current| + parse_text = lambda do |_, tokens, current| token = tokens[current] - return 1, [Node.new(NodeType::Text, nil, token.text, token.start, token.end)] + return 1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)] end # parameter := '{' + text* + '}' parse_parameter = parse_between( - NodeType::Parameter, - TokenType::BeginParameter, - TokenType::EndParameter, + NodeType::PARAMETER, + TokenType::BEGIN_PARAMETER, + TokenType::END_PARAMETER, [parse_text] ) # optional := '(' + option* + ')' # option := parameter | text parse_optional = parse_between( - NodeType::Optional, - TokenType::BeginOptional, - TokenType::EndOptional, + NodeType::OPTIONAL, + TokenType::BEGIN_OPTIONAL, + TokenType::END_OPTIONAL, [parse_parameter, parse_text] ) # alternation := alternative* + ( '/' + alternative* )+ - parse_alternative_separator = lambda do |expression, tokens, current| - unless looking_at(tokens, current, TokenType::Alternation) + parse_alternative_separator = lambda do |_, tokens, current| + unless looking_at(tokens, current, TokenType::ALTERNATION) return 0, nil end token = tokens[current] - return 1, [Node.new(NodeType::Alternative, nil, token.text, token.start, token.end)] + return 1, [Node.new(NodeType::ALTERNATIVE, nil, token.text, token.start, token.end)] end alternative_parsers = [ @@ -52,29 +52,29 @@ def parse(expression) # alternative: = optional | parameter | text parse_alternation = lambda do |expression, tokens, current| previous = current - 1 - unless looking_at_any(tokens, previous, [TokenType::StartOfLine, TokenType::WhiteSpace, TokenType::EndParameter]) + unless looking_at_any(tokens, previous, [TokenType::START_OF_LINE, TokenType::WHITE_SPACE, TokenType::END_PARAMETER]) return 0, nil end - consumed, ast = parse_tokens_until(expression, alternative_parsers, tokens, current, [TokenType::WhiteSpace, TokenType::EndOfLine, TokenType::BeginParameter]) + consumed, ast = parse_tokens_until(expression, alternative_parsers, tokens, current, [TokenType::WHITE_SPACE, TokenType::END_OF_LINE, TokenType::BEGIN_PARAMETER]) sub_current = current + consumed - unless ast.map { |astNode| astNode.type }.include? NodeType::Alternative + unless ast.map { |astNode| astNode.type }.include? NodeType::ALTERNATIVE return 0, nil end start = tokens[current].start _end = tokens[sub_current].start # Does not consume right hand boundary token - return consumed, [Node.new(NodeType::Alternation, split_alternatives(start, _end, ast), nil, start, _end)] + return consumed, [Node.new(NodeType::ALTERNATION, split_alternatives(start, _end, ast), nil, start, _end)] end # # cucumber-expression := ( alternation | optional | parameter | text )* # parse_cucumber_expression = parse_between( - NodeType::Expression, - TokenType::StartOfLine, - TokenType::EndOfLine, + NodeType::EXPRESSION, + TokenType::START_OF_LINE, + TokenType::END_OF_LINE, [parse_alternation, parse_optional, parse_parameter, parse_text] ) @@ -152,10 +152,10 @@ def looking_at(tokens, at, token) if at < 0 # If configured correctly this will never happen # Keep for completeness - return token == TokenType::StartOfLine + return token == TokenType::START_OF_LINE end if at >= tokens.length - return token == TokenType::EndOfLine + return token == TokenType::END_OF_LINE end tokens[at].type == token end @@ -165,7 +165,7 @@ def split_alternatives(start, _end, alternation) alternatives = [] alternative = [] alternation.each { |n| - if NodeType::Alternative == n.type + if NodeType::ALTERNATIVE == n.type separators.push(n) alternatives.push(alternative) alternative = [] @@ -182,14 +182,14 @@ def create_alternative_nodes(start, _end, separators, alternatives) alternatives.each_with_index do |n, i| if i == 0 right_separator = separators[i] - nodes.push(Node.new(NodeType::Alternative, n, nil, start, right_separator.start)) + nodes.push(Node.new(NodeType::ALTERNATIVE, n, nil, start, right_separator.start)) elsif i == alternatives.length - 1 left_separator = separators[i - 1] - nodes.push(Node.new(NodeType::Alternative, n, nil, left_separator.end, _end)) + nodes.push(Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, _end)) else left_separator = separators[i - 1] right_separator = separators[i] - nodes.push(Node.new(NodeType::Alternative, n, nil, left_separator.end, right_separator.start)) + nodes.push(Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, right_separator.start)) end end nodes diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb index c892d47112..3cd3378960 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb @@ -8,7 +8,7 @@ def tokenize(expression) @expression = expression tokens = [] @buffer = [] - previous_token_type = TokenType::StartOfLine + previous_token_type = TokenType::START_OF_LINE treat_as_text = false @escaped = 0 @buffer_start_index = 0 @@ -16,11 +16,11 @@ def tokenize(expression) codepoints = expression.codepoints if codepoints.empty? - tokens.push(Token.new(TokenType::StartOfLine, '', 0, 0)) + tokens.push(Token.new(TokenType::START_OF_LINE, '', 0, 0)) end codepoints.each do |codepoint| - if !treat_as_text && Token.isEscapeCharacter(codepoint) + if !treat_as_text && Token.is_escape_character(codepoint) @escaped += 1 treat_as_text = true next @@ -49,7 +49,7 @@ def tokenize(expression) end tokens.push( - Token.new(TokenType::EndOfLine, '', codepoints.length, codepoints.length) + Token.new(TokenType::END_OF_LINE, '', codepoints.length, codepoints.length) ) tokens end @@ -60,7 +60,7 @@ def tokenize(expression) def convert_buffer_to_token(token_type) escape_tokens = 0 - if token_type == TokenType::Text + if token_type == TokenType::TEXT escape_tokens = @escaped @escaped = 0 end @@ -79,10 +79,10 @@ def convert_buffer_to_token(token_type) def token_type_of(codepoint, treat_as_text) unless treat_as_text - return Token.typeOf(codepoint) + return Token.type_of(codepoint) end - if Token.canEscape(codepoint) - return TokenType::Text + if Token.can_escape(codepoint) + return TokenType::TEXT end raise CantEscape.new( @expression, @@ -92,7 +92,7 @@ def token_type_of(codepoint, treat_as_text) def should_create_new_token(previous_token_type, current_token_type) current_token_type != previous_token_type || - (current_token_type != TokenType::WhiteSpace && current_token_type != TokenType::Text) + (current_token_type != TokenType::WHITE_SPACE && current_token_type != TokenType::TEXT) end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb index f4ec37700d..e5568a4f29 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/errors.rb @@ -22,12 +22,12 @@ def build_message( m.strip end - def pointAt(index) + def point_at(index) ' ' * index + '^' end - def pointAtLocated(node) - pointer = [pointAt(node.start)] + def point_at_located(node) + pointer = [point_at(node.start)] if node.start + 1 < node.end for _ in node.start + 1...node.end - 1 pointer.push('-') @@ -43,7 +43,7 @@ def initialize(node, expression) super(build_message( node.start, expression, - pointAtLocated(node), + point_at_located(node), 'An alternative may not exclusively contain optionals', "If you did not mean to use an optional you can use '\\(' to escape the the '('" )) @@ -55,7 +55,7 @@ def initialize(node, expression) super(build_message( node.start, expression, - pointAtLocated(node), + point_at_located(node), 'Alternative may not be empty', "If you did not mean to use an alternative you can use '\\/' to escape the the '/'" )) @@ -67,7 +67,7 @@ def initialize(expression, index) super(build_message( index, expression, - pointAt(index), + point_at(index), "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped", "If you did mean to use an '\\' you can use '\\\\' to escape it" )) @@ -79,7 +79,7 @@ def initialize(node, expression) super(build_message( node.start, expression, - pointAtLocated(node), + point_at_located(node), 'An optional must contain some text', "If you did not mean to use an optional you can use '\\(' to escape the the '('" )) @@ -91,7 +91,7 @@ def initialize(node, expression) super(build_message( node.start, expression, - pointAtLocated(node), + point_at_located(node), 'An optional may not contain a parameter type', "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'" )) @@ -104,7 +104,7 @@ def initialize(expression) super(build_message( index, expression, - pointAt(index), + point_at(index), 'The end of line can not be escaped', "You can use '\\\\' to escape the the '\\'" )) @@ -112,16 +112,16 @@ def initialize(expression) end class MissingEndToken < CucumberExpressionError - def initialize(expression, beginToken, endToken, current) - beginSymbol = Token::symbolOf(beginToken) - endSymbol = Token::symbolOf(endToken) - purpose = Token::purposeOf(beginToken) + def initialize(expression, begin_token, end_token, current) + begin_symbol = Token::symbol_of(begin_token) + end_symbol = Token::symbol_of(end_token) + purpose = Token::purpose_of(begin_token) super(build_message( current.start, expression, - pointAtLocated(current), - "The '#{beginSymbol}' does not have a matching '#{endSymbol}'", - "If you did not intend to use #{purpose} you can use '\\#{beginSymbol}' to escape the #{purpose}" + point_at_located(current), + "The '#{begin_symbol}' does not have a matching '#{end_symbol}'", + "If you did not intend to use #{purpose} you can use '\\#{begin_symbol}' to escape the #{purpose}" )) end end @@ -132,7 +132,7 @@ def initialize(node, expression) super(build_message( node.start, expression, - pointAtLocated(node), + point_at_located(node), "Parameter names may not contain '[]()$.|?*+'", "Did you mean to use a regular expression?" )) @@ -143,7 +143,7 @@ class UndefinedParameterTypeError < CucumberExpressionError def initialize(node, expression, parameter_type_name) super(build_message(node.start, expression, - pointAtLocated(node), + point_at_located(node), "Undefined parameter type '#{parameter_type_name}'", "Please register a ParameterType for '#{parameter_type_name}'")) end From b977efb64d6d0c50e6fb09fbce7c504378d6a04e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 22 Nov 2020 15:03:47 +0100 Subject: [PATCH 175/183] Update docs --- cucumber-expressions/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 9f20f634da..44193c04e1 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -16,7 +16,7 @@ option := parameter | text parameter := '{' + text* + '}' text := token -token := '\' | whitespace | '(' | ')' | '{' | '}' | '/' | . +token := '\' | whitespace | '(' | ')' | '{' | '}' | '/' | any other text ``` Note: @@ -29,7 +29,7 @@ Note: ### Production Rules -The AST can be rewritten into a a regular expression by the following production +The AST can be rewritten into a regular expression by the following production rules: ``` @@ -43,8 +43,7 @@ parameter -> { '((?:' + parameter_pattern[0] + ')|(?:' ... + ')|(?:' + parameter_pattern[n-1] + '))' } text -> { - escape_regex := escape '^', `\`, `[` ,`(`, `{`, `$`, `.`, `|`, `?`, `*`, `+`, - `}`, `)` and `]` + escape_regex := escape '^', `$`, `[`, `]`, `(`, `)` `\`, `{`, `}`, `.`, `|`, `?`, `*`, `+` escape_regex(token.text) } ``` From 912c1ad93881c0105a716a130c1e29fc7f77eeb8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 23 Nov 2020 23:59:34 +0100 Subject: [PATCH 176/183] java: Explicitly forbid nested optionals and limit forbidden parameter name characters to reserved tokens --- cucumber-expressions/README.md | 18 +++-- .../CucumberExpression.java | 25 +++++-- .../CucumberExpressionException.java | 31 +++++++-- .../CucumberExpressionGenerator.java | 11 --- .../CucumberExpressionParser.java | 67 ++++++++++++++++--- .../cucumberexpressions/ParameterType.java | 2 +- .../CucumberExpressionTest.java | 19 +----- .../CustomParameterTypeTest.java | 4 +- .../optional-containing-escaped-optional.yaml | 14 ---- .../optional-containing-nested-optional.yaml | 15 +++++ ...oes-not-allow-alternation-in-optional.yaml | 9 +++ .../does-not-allow-nested-optional.yaml | 8 +++ ...rameter-name-with-reserved-characters.yaml | 10 +++ ...llow-parameter-type-with-left-bracket.yaml | 10 --- ...es-not-allow-unfinished-parenthesis-1.yaml | 8 +++ ...es-not-allow-unfinished-parenthesis-2.yaml | 8 +++ ...es-not-allow-unfinished-parenthesis-3.yaml | 8 +++ ...tches-alternation-in-optional-as-text.yaml | 4 -- .../optional-containing-escaped-optional.yaml | 14 ---- .../optional-containing-nested-optional.yaml | 15 +++++ ...oes-not-allow-alternation-in-optional.yaml | 9 +++ .../does-not-allow-nested-optional.yaml | 8 +++ ...rameter-name-with-reserved-characters.yaml | 10 +++ ...llow-parameter-type-with-left-bracket.yaml | 10 --- ...es-not-allow-unfinished-parenthesis-1.yaml | 8 +++ ...es-not-allow-unfinished-parenthesis-2.yaml | 8 +++ ...es-not-allow-unfinished-parenthesis-3.yaml | 8 +++ ...tches-alternation-in-optional-as-text.yaml | 4 -- 28 files changed, 250 insertions(+), 115 deletions(-) delete mode 100644 cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/java/testdata/ast/optional-containing-nested-optional.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-alternation-in-optional.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-nested-optional.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml delete mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml create mode 100644 cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml delete mode 100644 cucumber-expressions/java/testdata/expression/matches-alternation-in-optional-as-text.yaml delete mode 100644 cucumber-expressions/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/testdata/ast/optional-containing-nested-optional.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-alternation-in-optional.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-nested-optional.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml delete mode 100644 cucumber-expressions/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml create mode 100644 cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml delete mode 100644 cucumber-expressions/testdata/expression/matches-alternation-in-optional-as-text.yaml diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index 44193c04e1..6b6f9ea3d3 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -12,16 +12,24 @@ left-boundary := whitespace | } | ^ right-boundary := whitespace | { | $ alternative: = optional | parameter | text optional := '(' + option* + ')' -option := parameter | text -parameter := '{' + text* + '}' -text := token +option := optional | parameter | text +parameter := '{' + name* + '}' +name := whitespace | . +text := whitespace | ')' | '}' | . +``` -token := '\' | whitespace | '(' | ')' | '{' | '}' | '/' | any other text +The AST is constructed from the following tokens: +``` +escape := '\' +token := whitespace | '(' | ')' | '{' | '}' | '/' | . +. := any non-reserved codepoint ``` Note: * While `parameter` is allowed to appear as part of `alternative` and -`option` in the AST, such an AST is not a valid a Cucumber Expression. + `option` in the AST, such an AST is not a valid a Cucumber Expression. + * While `optional` is allowed to appear as part of `option` in the AST, + such an AST is not a valid a Cucumber Expression. * ASTs with empty alternatives or alternatives that only contain an optional are valid ASTs but invalid Cucumber Expressions. * All escaped tokens (tokens starting with a backslash) are rewritten to their diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 9a7c017114..d744705b9c 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -9,11 +9,13 @@ import java.util.function.Function; import java.util.regex.Pattern; +import static io.cucumber.cucumberexpressions.Ast.Node.Type.OPTIONAL_NODE; import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE; import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayNotBeEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayNotExclusivelyContainOptionals; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createInvalidParameterTypeName; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalIsNotAllowedInOptional; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInOptional; import static io.cucumber.cucumberexpressions.ParameterType.isValidParameterTypeName; @@ -65,6 +67,7 @@ private static String escapeRegex(String text) { private String rewriteOptional(Node node) { assertNoParameters(node, astNode -> createParameterIsNotAllowedInOptional(astNode, source)); + assertNoOptionals(node, astNode -> createOptionalIsNotAllowedInOptional(astNode, source)); assertNotEmpty(node, astNode -> createOptionalMayNotBeEmpty(astNode, source)); return node.nodes().stream() .map(this::rewriteToRegex) @@ -93,9 +96,6 @@ private String rewriteAlternative(Node node) { private String rewriteParameter(Node node) { String name = node.text(); - if (!isValidParameterTypeName(name)) { - throw createInvalidParameterTypeName(node ,source); - } ParameterType parameterType = parameterTypeRegistry.lookupByTypeName(name); if (parameterType == null) { throw createUndefinedParameterType(node, source, name); @@ -127,16 +127,27 @@ private void assertNotEmpty(Node node, private void assertNoParameters(Node node, Function createNodeContainedAParameterException) { + assertNoNodeOfType(PARAMETER_NODE, node, createNodeContainedAParameterException); + } + + private void assertNoOptionals(Node node, + Function createNodeContainedAnOptionalException) { + assertNoNodeOfType(OPTIONAL_NODE, node, createNodeContainedAnOptionalException); + } + + private void assertNoNodeOfType(Node.Type nodeType, Node node, + Function createException) { node.nodes() .stream() - .filter(astNode -> PARAMETER_NODE.equals(astNode.type())) - .map(createNodeContainedAParameterException) + .filter(astNode -> nodeType.equals(astNode.type())) + .map(createException) .findFirst() - .ifPresent(nodeContainedAParameterException -> { - throw nodeContainedAParameterException; + .ifPresent(exception -> { + throw exception; }); } + @Override public List> match(String text, Type... typeHints) { final Group group = treeRegexp.match(text); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java index d6961b2fcf..ef2a3cfc81 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionException.java @@ -28,6 +28,16 @@ static CucumberExpressionException createMissingEndToken(String expression, Type .symbol() + "' to escape the " + beginToken.purpose())); } + static CucumberExpressionException createAlternationNotAllowedInOptional(String expression, Token current) { + return new CucumberExpressionException(message( + current.start, + expression, + pointAt(current), + "An alternation can not be used inside an optional", + "You can use '\\/' to escape the the '/'" + )); + } + static CucumberExpressionException createTheEndOfLineCanNotBeEscaped(String expression) { int index = expression.codePointCount(0, expression.length()) - 1; return new CucumberExpressionException(message( @@ -56,6 +66,14 @@ static CucumberExpressionException createParameterIsNotAllowedInOptional(Node no "An optional may not contain a parameter type", "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'")); } + static CucumberExpressionException createOptionalIsNotAllowedInOptional(Node node, String expression) { + return new CucumberExpressionException(message( + node.start(), + expression, + pointAt(node), + "An optional may not contain an other optional", + "If you did not mean to use an optional type you can use '\\(' to escape the the '('. For more complicated expressions consider using a regular expression instead.")); + } static CucumberExpressionException createOptionalMayNotBeEmpty(Node node, String expression) { return new CucumberExpressionException(message( @@ -91,21 +109,24 @@ static CucumberExpressionException createCantEscape(String expression, int index static CucumberExpressionException createInvalidParameterTypeName(String name) { return new CucumberExpressionException( - "Illegal character in parameter name {" + name + "}. Parameter names may not contain '[]()$.|?*+'"); + "Illegal character in parameter name {" + name + "}. Parameter names may not contain '{', '}', '(', ')', '\\' or '/'"); } /** * Not very clear, but this message has to be language independent * Other languages have dedicated syntax for writing down regular expressions + *

+ * In java a regular expression has to start with {@code ^} and end with + * {@code $} to be recognized as one by Cucumber. * * @see ExpressionFactory */ - static CucumberExpressionException createInvalidParameterTypeName(Node node, String expression) { + static CucumberExpressionException createInvalidParameterTypeName(Token token, String expression) { return new CucumberExpressionException(message( - node.start(), + token.start(), expression, - pointAt(node), - "Parameter names may not contain '[]()$.|?*+'", + pointAt(token), + "Parameter names may not contain '{', '}', '(', ')', '\\' or '/'", "Did you mean to use a regular expression?")); } diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionGenerator.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionGenerator.java index 512f58e481..1cdd6fd3e4 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionGenerator.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionGenerator.java @@ -85,17 +85,6 @@ private String escape(String s) { .replaceAll("/", "\\\\/"); } - /** - * @param text the text (step) to generate an expression for - * @return the first of the generated expressions - * @deprecated use {@link #generateExpressions(String)} - */ - @Deprecated - public GeneratedExpression generateExpression(String text) { - List generatedExpressions = generateExpressions(text); - return generatedExpressions.get(0); - } - private List createParameterTypeMatchers(String text) { Collection> parameterTypes = parameterTypeRegistry.getParameterTypes(); List parameterTypeMatchers = new ArrayList<>(); diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 8912d9af73..60851db453 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -22,6 +22,8 @@ import static io.cucumber.cucumberexpressions.Ast.Token.Type.END_PARAMETER; import static io.cucumber.cucumberexpressions.Ast.Token.Type.START_OF_LINE; import static io.cucumber.cucumberexpressions.Ast.Token.Type.WHITE_SPACE; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternationNotAllowedInOptional; +import static io.cucumber.cucumberexpressions.CucumberExpressionException.createInvalidParameterTypeName; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createMissingEndToken; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -29,33 +31,76 @@ final class CucumberExpressionParser { /* - * text := token + * text := whitespace | ')' | '}' | . */ private static final Parser textParser = (expression, tokens, current) -> { Token token = tokens.get(current); - return new Result(1, new Node(TEXT_NODE, token.start(), token.end(), token.text)); + switch (token.type) { + case WHITE_SPACE: + case TEXT: + case END_PARAMETER: + case END_OPTIONAL: + return new Result(1, new Node(TEXT_NODE, token.start(), token.end(), token.text)); + case ALTERNATION: + throw createAlternationNotAllowedInOptional(expression, token); + case BEGIN_PARAMETER: + case START_OF_LINE: + case END_OF_LINE: + case BEGIN_OPTIONAL: + default: + // If configured correctly this will never happen + return new Result(0); + } }; /* - * parameter := '{' + text* + '}' + * name := whitespace | . + */ + private static final Parser nameParser = (expression, tokens, current) -> { + Token token = tokens.get(current); + switch (token.type) { + case WHITE_SPACE: + case TEXT: + return new Result(1, new Node(TEXT_NODE, token.start(), token.end(), token.text)); + case BEGIN_OPTIONAL: + case END_OPTIONAL: + case BEGIN_PARAMETER: + case END_PARAMETER: + case ALTERNATION: + throw createInvalidParameterTypeName(token, expression); + case START_OF_LINE: + case END_OF_LINE: + default: + // If configured correctly this will never happen + return new Result(0); + } + }; + + /* + * parameter := '{' + name* + '}' */ private static final Parser parameterParser = parseBetween( PARAMETER_NODE, BEGIN_PARAMETER, END_PARAMETER, - singletonList(textParser) + singletonList(nameParser) ); /* * optional := '(' + option* + ')' * option := parameter | text */ - private static final Parser optionalParser = parseBetween( - OPTIONAL_NODE, - BEGIN_OPTIONAL, - END_OPTIONAL, - asList(parameterParser, textParser) - ); + private static final Parser optionalParser; + static { + List parsers = new ArrayList<>(); + optionalParser = parseBetween( + OPTIONAL_NODE, + BEGIN_OPTIONAL, + END_OPTIONAL, + parsers + ); + parsers.addAll(asList(optionalParser, parameterParser, textParser)); + } /* * alternation := alternative* + ( '/' + alternative* )+ @@ -152,7 +197,7 @@ private static Parser parseBetween( return new Result(0); } int subCurrent = current + 1; - Result result = parseTokensUntil(expression, parsers, tokens, subCurrent, endToken); + Result result = parseTokensUntil(expression, parsers, tokens, subCurrent, endToken, END_OF_LINE); subCurrent += result.consumed; // endToken not found diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java index 06538791e8..c3f8348073 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java @@ -12,7 +12,7 @@ @API(status = API.Status.STABLE) public final class ParameterType implements Comparable> { @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces - private static final Pattern ILLEGAL_PARAMETER_NAME_PATTERN = Pattern.compile("([\\[\\]()$.|?*+])"); + private static final Pattern ILLEGAL_PARAMETER_NAME_PATTERN = Pattern.compile("([{}()\\\\/])"); private static final Pattern UNESCAPE_PATTERN = Pattern.compile("(\\\\([\\[$.|?*+\\]]))"); private final String name; diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 8ee94ffe7b..b72a126143 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -26,6 +26,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -69,7 +70,7 @@ void expression_acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.cl expression.match(expectation.getText()); }; CucumberExpressionException exception = assertThrows(CucumberExpressionException.class, executable); - assertThat(exception.getMessage(), Matchers.is(expectation.getException())); + assertThat(exception.getMessage(), equalTo(expectation.getException())); } } @@ -82,22 +83,6 @@ void regex_acceptance_tests_pass(@ConvertWith(FileToExpectationConverter.class) // Misc tests - @Test - void alternation_separator_can_be_used_in_parameter() { - parameterTypeRegistry - .defineParameterType(new ParameterType<>("a/b", "(.*)", String.class, (String arg) -> arg)); - assertEquals(singletonList("three mice"), - match("{a/b}", "three mice")); - } - - @Test - void matches_escaped_parenthesis_4() { - parameterTypeRegistry - .defineParameterType(new ParameterType<>("{string}", "\"(.*)\"", String.class, (String arg) -> arg)); - assertEquals(singletonList("blind"), - match("three ((exceptionally\\)) {{string\\}} mice", "three (exceptionally) \"blind\" mice")); - } - @Test void exposes_source() { String expr = "I have {int} cuke(s)"; diff --git a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java index 6239035a31..81eb19cead 100644 --- a/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java +++ b/cucumber-expressions/java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java @@ -66,7 +66,7 @@ public void create_parameter() { public void throws_exception_for_illegal_character_in_parameter_name() { final Executable testMethod = () -> new ParameterType<>( - "[string]", + "(string)", ".*", String.class, (Transformer) s -> s, @@ -75,7 +75,7 @@ public void throws_exception_for_illegal_character_in_parameter_name() { ); final CucumberExpressionException thrownException = assertThrows(CucumberExpressionException.class, testMethod); - assertThat(thrownException.getMessage(), is(equalTo("Illegal character in parameter name {[string]}. Parameter names may not contain '[]()$.|?*+'"))); + assertThat(thrownException.getMessage(), is(equalTo("Illegal character in parameter name {(string)}. Parameter names may not contain '{', '}', '(', ')', '\\' or '/'"))); } @Test diff --git a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml deleted file mode 100644 index f09199a454..0000000000 --- a/cucumber-expressions/java/testdata/ast/optional-containing-escaped-optional.yaml +++ /dev/null @@ -1,14 +0,0 @@ -expression: three ((very\) blind) mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ - {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, - {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, - {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, - {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} - ]}, - {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, - {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} - ]} diff --git a/cucumber-expressions/java/testdata/ast/optional-containing-nested-optional.yaml b/cucumber-expressions/java/testdata/ast/optional-containing-nested-optional.yaml new file mode 100644 index 0000000000..0fdd55d46b --- /dev/null +++ b/cucumber-expressions/java/testdata/ast/optional-containing-nested-optional.yaml @@ -0,0 +1,15 @@ +expression: three ((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 25, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 20, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 7, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 8, "end": 12, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 19, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": " "}, + {"type": "TEXT_NODE", "start": 21, "end": 25, "token": "mice"} + ]} diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-in-optional.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-in-optional.yaml new file mode 100644 index 0000000000..b507e27220 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-alternation-in-optional.yaml @@ -0,0 +1,9 @@ +expression: three( brown/black) mice +text: three brown/black mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three( brown/black) mice + ^ + An alternation can not be used inside an optional. + You can use '\/' to escape the the '/' diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-nested-optional.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-nested-optional.yaml new file mode 100644 index 0000000000..017c3be25d --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-nested-optional.yaml @@ -0,0 +1,8 @@ +expression: "(a(b))" +exception: |- + This Cucumber Expression has a problem at column 3: + + (a(b)) + ^-^ + An optional may not contain an other optional. + If you did not mean to use an optional type you can use '\(' to escape the the '('. For more complicated expressions consider using a regular expression instead. diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml new file mode 100644 index 0000000000..d1c89689e9 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml @@ -0,0 +1,10 @@ +expression: |- + {(string)} +text: something +exception: |- + This Cucumber Expression has a problem at column 2: + + {(string)} + ^ + Parameter names may not contain '{', '}', '(', ')', '\' or '/'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml deleted file mode 100644 index 1dd65aa276..0000000000 --- a/cucumber-expressions/java/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: |- - {[string]} -text: something -exception: |- - This Cucumber Expression has a problem at column 1: - - {[string]} - ^--------^ - Parameter names may not contain '[]()$.|?*+'. - Did you mean to use a regular expression? diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml new file mode 100644 index 0000000000..e033648972 --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string\} mice +exception: |- + This Cucumber Expression has a problem at column 24: + + three (exceptionally\) {string\} mice + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml new file mode 100644 index 0000000000..7cb9c6d56a --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string} mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (exceptionally\) {string} mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml b/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml new file mode 100644 index 0000000000..029c4e63bd --- /dev/null +++ b/cucumber-expressions/java/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml @@ -0,0 +1,8 @@ +expression: three ((exceptionally\) strong) mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three ((exceptionally\) strong) mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/java/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/java/testdata/expression/matches-alternation-in-optional-as-text.yaml deleted file mode 100644 index 15fe78bf53..0000000000 --- a/cucumber-expressions/java/testdata/expression/matches-alternation-in-optional-as-text.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three( brown/black) mice -text: three brown/black mice -expected: |- - [] diff --git a/cucumber-expressions/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/testdata/ast/optional-containing-escaped-optional.yaml deleted file mode 100644 index f09199a454..0000000000 --- a/cucumber-expressions/testdata/ast/optional-containing-escaped-optional.yaml +++ /dev/null @@ -1,14 +0,0 @@ -expression: three ((very\) blind) mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ - {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, - {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, - {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, - {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} - ]}, - {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, - {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} - ]} diff --git a/cucumber-expressions/testdata/ast/optional-containing-nested-optional.yaml b/cucumber-expressions/testdata/ast/optional-containing-nested-optional.yaml new file mode 100644 index 0000000000..0fdd55d46b --- /dev/null +++ b/cucumber-expressions/testdata/ast/optional-containing-nested-optional.yaml @@ -0,0 +1,15 @@ +expression: three ((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 25, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 20, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 7, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 8, "end": 12, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 19, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": " "}, + {"type": "TEXT_NODE", "start": 21, "end": 25, "token": "mice"} + ]} diff --git a/cucumber-expressions/testdata/expression/does-not-allow-alternation-in-optional.yaml b/cucumber-expressions/testdata/expression/does-not-allow-alternation-in-optional.yaml new file mode 100644 index 0000000000..b507e27220 --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-alternation-in-optional.yaml @@ -0,0 +1,9 @@ +expression: three( brown/black) mice +text: three brown/black mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three( brown/black) mice + ^ + An alternation can not be used inside an optional. + You can use '\/' to escape the the '/' diff --git a/cucumber-expressions/testdata/expression/does-not-allow-nested-optional.yaml b/cucumber-expressions/testdata/expression/does-not-allow-nested-optional.yaml new file mode 100644 index 0000000000..017c3be25d --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-nested-optional.yaml @@ -0,0 +1,8 @@ +expression: "(a(b))" +exception: |- + This Cucumber Expression has a problem at column 3: + + (a(b)) + ^-^ + An optional may not contain an other optional. + If you did not mean to use an optional type you can use '\(' to escape the the '('. For more complicated expressions consider using a regular expression instead. diff --git a/cucumber-expressions/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml b/cucumber-expressions/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml new file mode 100644 index 0000000000..d1c89689e9 --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml @@ -0,0 +1,10 @@ +expression: |- + {(string)} +text: something +exception: |- + This Cucumber Expression has a problem at column 2: + + {(string)} + ^ + Parameter names may not contain '{', '}', '(', ')', '\' or '/'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml deleted file mode 100644 index 1dd65aa276..0000000000 --- a/cucumber-expressions/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: |- - {[string]} -text: something -exception: |- - This Cucumber Expression has a problem at column 1: - - {[string]} - ^--------^ - Parameter names may not contain '[]()$.|?*+'. - Did you mean to use a regular expression? diff --git a/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml b/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml new file mode 100644 index 0000000000..e033648972 --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string\} mice +exception: |- + This Cucumber Expression has a problem at column 24: + + three (exceptionally\) {string\} mice + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml b/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml new file mode 100644 index 0000000000..7cb9c6d56a --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string} mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (exceptionally\) {string} mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml b/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml new file mode 100644 index 0000000000..029c4e63bd --- /dev/null +++ b/cucumber-expressions/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml @@ -0,0 +1,8 @@ +expression: three ((exceptionally\) strong) mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three ((exceptionally\) strong) mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/testdata/expression/matches-alternation-in-optional-as-text.yaml deleted file mode 100644 index 15fe78bf53..0000000000 --- a/cucumber-expressions/testdata/expression/matches-alternation-in-optional-as-text.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three( brown/black) mice -text: three brown/black mice -expected: |- - [] From 2545091e690e5ce168e03c99a56f58be65207098 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 27 Nov 2020 16:57:35 +0100 Subject: [PATCH 177/183] go: Explicitly forbid nested optionals and limit forbidden parameter name characters to reserved tokens --- .../go/cucumber_expression.go | 22 ++++- .../go/cucumber_expression_parser.go | 84 ++++++++++++++++--- .../go/cucumber_expression_test.go | 11 --- .../go/custom_parameter_type_test.go | 4 +- cucumber-expressions/go/errors.go | 30 +++++-- cucumber-expressions/go/parameter_type.go | 6 +- .../optional-containing-escaped-optional.yaml | 14 ---- .../optional-containing-nested-optional.yaml | 15 ++++ ...oes-not-allow-alternation-in-optional.yaml | 9 ++ .../does-not-allow-nested-optional.yaml | 8 ++ ...rameter-name-with-reserved-characters.yaml | 10 +++ ...llow-parameter-type-with-left-bracket.yaml | 10 --- ...es-not-allow-unfinished-parenthesis-1.yaml | 8 ++ ...es-not-allow-unfinished-parenthesis-2.yaml | 8 ++ ...es-not-allow-unfinished-parenthesis-3.yaml | 8 ++ ...tches-alternation-in-optional-as-text.yaml | 4 - .../CucumberExpressionParser.java | 2 +- 17 files changed, 186 insertions(+), 67 deletions(-) delete mode 100644 cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/go/testdata/ast/optional-containing-nested-optional.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-alternation-in-optional.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-nested-optional.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml create mode 100644 cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml delete mode 100644 cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 86e59c1547..626384b4ff 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -61,6 +61,10 @@ func (c *CucumberExpression) rewriteOptional(node node) (string, error) { if err != nil { return "", err } + err = c.assertNoOptionals(node, c.createOptionalIsNotAllowedInOptional()) + if err != nil { + return "", err + } err = c.assertNotEmpty(node, c.createOptionalMayNotBeEmpty()) if err != nil { return "", err @@ -74,6 +78,12 @@ func (c *CucumberExpression) createParameterIsNotAllowedInOptional() func(node) } } +func (c *CucumberExpression) createOptionalIsNotAllowedInOptional() func(node) error { + return func(node node) error { + return createOptionalIsNotAllowedInOptional(node, c.source) + } +} + func (c *CucumberExpression) createOptionalMayNotBeEmpty() func(node) error { return func(node node) error { return createOptionalMayNotBeEmpty(node, c.source) @@ -118,9 +128,6 @@ func (c *CucumberExpression) rewriteParameter(node node) (string, error) { return fmt.Sprintf("(%s)", strings.Join(captureGroups, "|")) } typeName := node.text() - if isValidParameterTypeName(typeName) { - return "", createInvalidParameterTypeNameInNode(node, c.source) - } parameterType := c.parameterTypeRegistry.LookupByTypeName(typeName) if parameterType == nil { return "", createUndefinedParameterType(node, c.source, typeName) @@ -168,6 +175,15 @@ func (c *CucumberExpression) assertNoParameters(node node, createParameterIsNotA return nil } +func (c *CucumberExpression) assertNoOptionals(node node, createOptionalIsNotAllowedInOptionalError func(node) error) error { + for _, node := range node.Nodes { + if node.NodeType == optionalNode { + return createOptionalIsNotAllowedInOptionalError(node) + } + } + return nil +} + func (c *CucumberExpression) Match(text string, typeHints ...reflect.Type) ([]*Argument, error) { hintOrDefault := func(i int, typeHints ...reflect.Type) reflect.Type { typeHint := reflect.TypeOf("") diff --git a/cucumber-expressions/go/cucumber_expression_parser.go b/cucumber-expressions/go/cucumber_expression_parser.go index 66e33b49d2..e2982fa40b 100644 --- a/cucumber-expressions/go/cucumber_expression_parser.go +++ b/cucumber-expressions/go/cucumber_expression_parser.go @@ -1,35 +1,96 @@ package cucumberexpressions /* - * text := token + * text := whitespace | ')' | '}' | . */ var textParser = func(expression string, tokens []token, current int) (int, node, error) { token := tokens[current] - return 1, node{textNode, token.Start, token.End, token.Text, nil}, nil + switch token.TokenType { + case whiteSpace: + fallthrough + case text: + fallthrough + case endParameter: + fallthrough + case endOptional: + return 1, node{textNode, token.Start, token.End, token.Text, nil}, nil + case alternation: + return 0, nullNode, createAlternationNotAllowedInOptional(expression, token) + case beginParameter: + fallthrough + case startOfLine: + fallthrough + case endOfLine: + fallthrough + case beginOptional: + fallthrough + default: + // If configured correctly this will never happen + return 0, nullNode, nil + } +} + +/* + * name := whitespace | . + */ +var nameParser = func(expression string, tokens []token, current int) (int, node, error) { + token := tokens[current] + switch token.TokenType { + case whiteSpace: + fallthrough + case text: + return 1, node{textNode, token.Start, token.End, token.Text, nil}, nil + case beginParameter: + fallthrough + case endParameter: + fallthrough + case beginOptional: + fallthrough + case endOptional: + fallthrough + case alternation: + return 0, nullNode, createInvalidParameterTypeNameInNode(token, expression) + case startOfLine: + fallthrough + case endOfLine: + fallthrough + default: + // If configured correctly this will never happen + return 0, nullNode, nil + } } /* - * parameter := '{' + text* + '}' + * parameter := '{' + name* + '}' */ var parameterParser = parseBetween( parameterNode, beginParameter, endParameter, - textParser, + []parser{nameParser}, ) /* * optional := '(' + option* + ')' - * option := parameter | text + * option := optional | parameter | text */ +var optionalSubParsers = make([]parser, 3) var optionalParser = parseBetween( optionalNode, beginOptional, endOptional, - parameterParser, - textParser, + optionalSubParsers, ) +func setParsers(parsers []parser, toSet ...parser) []parser { + for i, p := range toSet { + parsers[i] = p + } + return parsers +} + +var _ = setParsers(optionalSubParsers, optionalParser, parameterParser, textParser) + // alternation := alternative* + ( '/' + alternative* )+ var alternativeSeparatorParser = func(expression string, tokens []token, current int) (int, node, error) { if !lookingAt(tokens, current, alternation) { @@ -89,10 +150,7 @@ var cucumberExpressionParser = parseBetween( expressionNode, startOfLine, endOfLine, - alternationParser, - optionalParser, - parameterParser, - textParser, + []parser{alternationParser, optionalParser, parameterParser, textParser}, ) func parse(expression string) (node, error) { @@ -113,14 +171,14 @@ func parse(expression string) (node, error) { type parser func(expression string, tokens []token, current int) (int, node, error) -func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, parsers ...parser) parser { +func parseBetween(nodeType nodeType, beginToken tokenType, endToken tokenType, parsers []parser) parser { return func(expression string, tokens []token, current int) (int, node, error) { if !lookingAt(tokens, current, beginToken) { return 0, nullNode, nil } subCurrent := current + 1 - consumed, subAst, err := parseTokensUntil(expression, parsers, tokens, subCurrent, endToken) + consumed, subAst, err := parseTokensUntil(expression, parsers, tokens, subCurrent, endToken, endOfLine) if err != nil { return 0, nullNode, err } diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 91984e6f87..122152cbdc 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -203,17 +203,6 @@ func TestCucumberExpression(t *testing.T) { func MatchCucumberExpression(t *testing.T, expr string, text string, typeHints ...reflect.Type) []interface{} { parameterTypeRegistry := NewParameterTypeRegistry() - parameterType1, err := NewParameterType( - "{string}", - []*regexp.Regexp{regexp.MustCompile(`".*"`)}, - "string", - nil, - true, - false, - false, - ) - require.NoError(t, err) - require.NoError(t, parameterTypeRegistry.DefineParameterType(parameterType1)) expression, err := NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) args, err := expression.Match(text, typeHints...) diff --git a/cucumber-expressions/go/custom_parameter_type_test.go b/cucumber-expressions/go/custom_parameter_type_test.go index 39d7e132a9..6b7837dad7 100644 --- a/cucumber-expressions/go/custom_parameter_type_test.go +++ b/cucumber-expressions/go/custom_parameter_type_test.go @@ -42,7 +42,7 @@ func CreateParameterTypeRegistry(t *testing.T) *ParameterTypeRegistry { func TestCustomParameterTypes(t *testing.T) { t.Run("throws exception for illegal character in parameter type name", func(t *testing.T) { _, err := NewParameterType( - "[string]", + "{string}", []*regexp.Regexp{regexp.MustCompile(`.*`)}, "x", func(args ...*string) interface{} { @@ -53,7 +53,7 @@ func TestCustomParameterTypes(t *testing.T) { false, ) require.Error(t, err) - require.Equal(t, "Illegal character in parameter name {[string]}. Parameter names may not contain '[]()$.|?*+'", err.Error()) + require.Equal(t, "Illegal character in parameter name {{string}}. Parameter names may not contain '{', '}', '(', ')', '\\' or '/'", err.Error()) }) t.Run("CucumberExpression", func(t *testing.T) { diff --git a/cucumber-expressions/go/errors.go b/cucumber-expressions/go/errors.go index 8ebda02e6a..811e6421d6 100644 --- a/cucumber-expressions/go/errors.go +++ b/cucumber-expressions/go/errors.go @@ -28,6 +28,16 @@ func createMissingEndToken(expression string, beginToken tokenType, endToken tok )) } +func createAlternationNotAllowedInOptional(expression string, current token) error { + return NewCucumberExpressionError(message( + current.Start, + expression, + pointAtToken(current), + "An alternation can not be used inside an optional", + "You can use '\\/' to escape the the '/'", + )) +} + func createTheEndOfLineCanNotBeEscaped(expression string) error { index := utf8.RuneCountInString(expression) - 1 return NewCucumberExpressionError(message( @@ -57,7 +67,15 @@ func createParameterIsNotAllowedInOptional(node node, expression string) error { "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'", )) } - +func createOptionalIsNotAllowedInOptional(node node, expression string) error { + return NewCucumberExpressionError(message( + node.Start, + expression, + pointAtNode(node), + "An optional may not contain an other optional", + "If you did not mean to use an optional type you can use '\\(' to escape the the '('. For more complicated expressions consider using a regular expression instead.", + )) +} func createAlternativeMayNotBeEmpty(node node, expression string) error { return NewCucumberExpressionError(message( node.Start, @@ -88,16 +106,16 @@ func createCantEscaped(expression string, index int) error { } func createInvalidParameterTypeName(typeName string) error { - return NewCucumberExpressionError("Illegal character in parameter name {" + typeName + "}. Parameter names may not contain '[]()$.|?*+'") + return NewCucumberExpressionError("Illegal character in parameter name {" + typeName + "}. Parameter names may not contain '{', '}', '(', ')', '\\' or '/'") } // Not very clear, but this message has to be language independent -func createInvalidParameterTypeNameInNode(node node, expression string) error { +func createInvalidParameterTypeNameInNode(token token, expression string) error { return NewCucumberExpressionError(message( - node.Start, + token.Start, expression, - pointAtNode(node), - "Parameter names may not contain '[]()$.|?*+'", + pointAtToken(token), + "Parameter names may not contain '{', '}', '(', ')', '\\' or '/'", "Did you mean to use a regular expression?", )) } diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go index ee00a38cbc..ee4dbb6cd0 100644 --- a/cucumber-expressions/go/parameter_type.go +++ b/cucumber-expressions/go/parameter_type.go @@ -7,7 +7,7 @@ import ( ) var HAS_FLAG_REGEXP = regexp.MustCompile(`\(\?[imsU-]+(:.*)?\)`) -var ILLEGAL_PARAMETER_NAME_REGEXP = regexp.MustCompile(`([\[\]()$.|?*+])`) +var ILLEGAL_PARAMETER_NAME_REGEXP = regexp.MustCompile(`([{}()\\/])`) type ParameterType struct { name string @@ -20,14 +20,14 @@ type ParameterType struct { } func CheckParameterTypeName(typeName string) error { - if isValidParameterTypeName(typeName) { + if !isValidParameterTypeName(typeName) { return createInvalidParameterTypeName(typeName) } return nil } func isValidParameterTypeName(typeName string) bool { - return ILLEGAL_PARAMETER_NAME_REGEXP.MatchString(typeName) + return !ILLEGAL_PARAMETER_NAME_REGEXP.MatchString(typeName) } func NewParameterType(name string, regexps []*regexp.Regexp, type1 string, transform func(...*string) interface{}, useForSnippets bool, preferForRegexpMatch bool, useRegexpMatchAsStrongTypeHint bool) (*ParameterType, error) { diff --git a/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml deleted file mode 100644 index f09199a454..0000000000 --- a/cucumber-expressions/go/testdata/ast/optional-containing-escaped-optional.yaml +++ /dev/null @@ -1,14 +0,0 @@ -expression: three ((very\) blind) mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ - {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, - {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, - {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, - {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} - ]}, - {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, - {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} - ]} diff --git a/cucumber-expressions/go/testdata/ast/optional-containing-nested-optional.yaml b/cucumber-expressions/go/testdata/ast/optional-containing-nested-optional.yaml new file mode 100644 index 0000000000..0fdd55d46b --- /dev/null +++ b/cucumber-expressions/go/testdata/ast/optional-containing-nested-optional.yaml @@ -0,0 +1,15 @@ +expression: three ((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 25, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 20, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 7, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 8, "end": 12, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 19, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": " "}, + {"type": "TEXT_NODE", "start": 21, "end": 25, "token": "mice"} + ]} diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-in-optional.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-in-optional.yaml new file mode 100644 index 0000000000..b507e27220 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-alternation-in-optional.yaml @@ -0,0 +1,9 @@ +expression: three( brown/black) mice +text: three brown/black mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three( brown/black) mice + ^ + An alternation can not be used inside an optional. + You can use '\/' to escape the the '/' diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-nested-optional.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-nested-optional.yaml new file mode 100644 index 0000000000..017c3be25d --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-nested-optional.yaml @@ -0,0 +1,8 @@ +expression: "(a(b))" +exception: |- + This Cucumber Expression has a problem at column 3: + + (a(b)) + ^-^ + An optional may not contain an other optional. + If you did not mean to use an optional type you can use '\(' to escape the the '('. For more complicated expressions consider using a regular expression instead. diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml new file mode 100644 index 0000000000..d1c89689e9 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml @@ -0,0 +1,10 @@ +expression: |- + {(string)} +text: something +exception: |- + This Cucumber Expression has a problem at column 2: + + {(string)} + ^ + Parameter names may not contain '{', '}', '(', ')', '\' or '/'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml deleted file mode 100644 index 1dd65aa276..0000000000 --- a/cucumber-expressions/go/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: |- - {[string]} -text: something -exception: |- - This Cucumber Expression has a problem at column 1: - - {[string]} - ^--------^ - Parameter names may not contain '[]()$.|?*+'. - Did you mean to use a regular expression? diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml new file mode 100644 index 0000000000..e033648972 --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string\} mice +exception: |- + This Cucumber Expression has a problem at column 24: + + three (exceptionally\) {string\} mice + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml new file mode 100644 index 0000000000..7cb9c6d56a --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string} mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (exceptionally\) {string} mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml b/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml new file mode 100644 index 0000000000..029c4e63bd --- /dev/null +++ b/cucumber-expressions/go/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml @@ -0,0 +1,8 @@ +expression: three ((exceptionally\) strong) mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three ((exceptionally\) strong) mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml deleted file mode 100644 index 15fe78bf53..0000000000 --- a/cucumber-expressions/go/testdata/expression/matches-alternation-in-optional-as-text.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three( brown/black) mice -text: three brown/black mice -expected: |- - [] diff --git a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java index 60851db453..b77be40e91 100644 --- a/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java +++ b/cucumber-expressions/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpressionParser.java @@ -88,7 +88,7 @@ final class CucumberExpressionParser { /* * optional := '(' + option* + ')' - * option := parameter | text + * option := optional | parameter | text */ private static final Parser optionalParser; static { From f0ba7b21f06b2f95678d93a0cfdba6590833081c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 28 Nov 2020 17:12:13 +0100 Subject: [PATCH 178/183] js: Explicitly forbid nested optionals and limit forbidden parameter name characters to reserved tokens --- .../javascript/src/CucumberExpression.ts | 23 +++-- .../src/CucumberExpressionParser.ts | 88 ++++++++++++++++--- cucumber-expressions/javascript/src/Errors.ts | 47 +++++++++- .../javascript/src/ParameterType.ts | 9 +- .../test/CustomParameterTypeTest.ts | 2 +- .../optional-containing-escaped-optional.yaml | 14 --- .../optional-containing-nested-optional.yaml | 15 ++++ ...oes-not-allow-alternation-in-optional.yaml | 9 ++ .../does-not-allow-nested-optional.yaml | 8 ++ ...rameter-name-with-reserved-characters.yaml | 10 +++ ...llow-parameter-type-with-left-bracket.yaml | 10 --- ...es-not-allow-unfinished-parenthesis-1.yaml | 8 ++ ...es-not-allow-unfinished-parenthesis-2.yaml | 8 ++ ...es-not-allow-unfinished-parenthesis-3.yaml | 8 ++ ...tches-alternation-in-optional-as-text.yaml | 4 - .../optional-containing-escaped-optional.yaml | 14 --- .../optional-containing-nested-optional.yaml | 15 ++++ ...oes-not-allow-alternation-in-optional.yaml | 9 ++ .../does-not-allow-nested-optional.yaml | 8 ++ ...rameter-name-with-reserved-characters.yaml | 10 +++ ...llow-parameter-type-with-left-bracket.yaml | 10 --- ...es-not-allow-unfinished-parenthesis-1.yaml | 8 ++ ...es-not-allow-unfinished-parenthesis-2.yaml | 8 ++ ...es-not-allow-unfinished-parenthesis-3.yaml | 8 ++ ...tches-alternation-in-optional-as-text.yaml | 4 - 25 files changed, 276 insertions(+), 81 deletions(-) delete mode 100644 cucumber-expressions/javascript/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/ast/optional-containing-nested-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-in-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-nested-optional.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml delete mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml create mode 100644 cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml delete mode 100644 cucumber-expressions/javascript/testdata/expression/matches-alternation-in-optional-as-text.yaml delete mode 100644 cucumber-expressions/ruby/testdata/ast/optional-containing-escaped-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/ast/optional-containing-nested-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-in-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-nested-optional.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml delete mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml create mode 100644 cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml delete mode 100644 cucumber-expressions/ruby/testdata/expression/matches-alternation-in-optional-as-text.yaml diff --git a/cucumber-expressions/javascript/src/CucumberExpression.ts b/cucumber-expressions/javascript/src/CucumberExpression.ts index 02fc4a2b3b..d00915f4c7 100644 --- a/cucumber-expressions/javascript/src/CucumberExpression.ts +++ b/cucumber-expressions/javascript/src/CucumberExpression.ts @@ -5,7 +5,7 @@ import Argument from './Argument' import { createAlternativeMayNotBeEmpty, createAlternativeMayNotExclusivelyContainOptionals, - createInvalidParameterTypeName, + createOptionalIsNotAllowedInOptional, createOptionalMayNotBeEmpty, createParameterIsNotAllowedInOptional, createUndefinedParameterType, @@ -63,6 +63,9 @@ export default class CucumberExpression implements Expression { this.assertNoParameters(node, (astNode) => createParameterIsNotAllowedInOptional(astNode, this.expression) ) + this.assertNoOptionals(node, (astNode) => + createOptionalIsNotAllowedInOptional(astNode, this.expression) + ) this.assertNotEmpty(node, (astNode) => createOptionalMayNotBeEmpty(astNode, this.expression) ) @@ -93,10 +96,6 @@ export default class CucumberExpression implements Expression { private rewriteParameter(node: Node) { const name = node.text() - if (!ParameterType.isValidParameterTypeName(name)) { - throw createInvalidParameterTypeName(node, this.expression) - } - const parameterType = this.parameterTypeRegistry.lookupByTypeName(name) if (!parameterType) { throw createUndefinedParameterType(node, this.expression, name) @@ -141,6 +140,20 @@ export default class CucumberExpression implements Expression { } } + private assertNoOptionals( + node: Node, + createNodeContainedAnOptionalError: ( + astNode: Node + ) => CucumberExpressionError + ) { + const parameterNodes = node.nodes.filter( + (astNode) => NodeType.optional == astNode.type + ) + if (parameterNodes.length > 0) { + throw createNodeContainedAnOptionalError(parameterNodes[0]) + } + } + public match(text: string): ReadonlyArray> { return Argument.build(this.treeRegexp, text, this.parameterTypes) } diff --git a/cucumber-expressions/javascript/src/CucumberExpressionParser.ts b/cucumber-expressions/javascript/src/CucumberExpressionParser.ts index 68b681a181..2bd41e3927 100644 --- a/cucumber-expressions/javascript/src/CucumberExpressionParser.ts +++ b/cucumber-expressions/javascript/src/CucumberExpressionParser.ts @@ -1,9 +1,13 @@ import { Node, NodeType, Token, TokenType } from './Ast' import CucumberExpressionTokenizer from './CucumberExpressionTokenizer' -import { createMissingEndToken } from './Errors' +import { + createAlternationNotAllowedInOptional, + createInvalidParameterTypeNameInNode, + createMissingEndToken, +} from './Errors' /* - * text := token + * text := whitespace | ')' | '}' | . */ function parseText( expression: string, @@ -11,11 +15,70 @@ function parseText( current: number ) { const token = tokens[current] - return { - consumed: 1, - ast: [ - new Node(NodeType.text, undefined, token.text, token.start, token.end), - ], + switch (token.type) { + case TokenType.whiteSpace: + case TokenType.text: + case TokenType.endParameter: + case TokenType.endOptional: + return { + consumed: 1, + ast: [ + new Node( + NodeType.text, + undefined, + token.text, + token.start, + token.end + ), + ], + } + case TokenType.alternation: + throw createAlternationNotAllowedInOptional(expression, token) + case TokenType.startOfLine: + case TokenType.endOfLine: + case TokenType.beginOptional: + case TokenType.beginParameter: + default: + // If configured correctly this will never happen + return { consumed: 0 } + } +} + +/* + * parameter := '{' + name* + '}' + */ +function parseName( + expression: string, + tokens: ReadonlyArray, + current: number +) { + const token = tokens[current] + switch (token.type) { + case TokenType.whiteSpace: + case TokenType.text: + return { + consumed: 1, + ast: [ + new Node( + NodeType.text, + undefined, + token.text, + token.start, + token.end + ), + ], + } + case TokenType.beginOptional: + case TokenType.endOptional: + case TokenType.beginParameter: + case TokenType.endParameter: + case TokenType.alternation: + throw createInvalidParameterTypeNameInNode(token, expression) + case TokenType.startOfLine: + case TokenType.endOfLine: + default: + // If configured correctly this will never happen + return { consumed: 0 } } } @@ -26,19 +89,21 @@ const parseParameter = parseBetween( NodeType.parameter, TokenType.beginParameter, TokenType.endParameter, - [parseText] + [parseName] ) /* * optional := '(' + option* + ')' - * option := parameter | text + * option := optional | parameter | text */ +const optionalSubParsers: Array = [] const parseOptional = parseBetween( NodeType.optional, TokenType.beginOptional, TokenType.endOptional, - [parseParameter, parseText] + optionalSubParsers ) +optionalSubParsers.push(parseOptional, parseParameter, parseText) /* * alternation := alternative* + ( '/' + alternative* )+ @@ -152,7 +217,7 @@ function parseBetween( type: NodeType, beginToken: TokenType, endToken: TokenType, - parsers: ReadonlyArray + parsers: Array ): Parser { return (expression, tokens, current) => { if (!lookingAt(tokens, current, beginToken)) { @@ -161,6 +226,7 @@ function parseBetween( let subCurrent = current + 1 const result = parseTokensUntil(expression, parsers, tokens, subCurrent, [ endToken, + TokenType.endOfLine, ]) subCurrent += result.consumed diff --git a/cucumber-expressions/javascript/src/Errors.ts b/cucumber-expressions/javascript/src/Errors.ts index 1e72f2b414..94effbf6e8 100644 --- a/cucumber-expressions/javascript/src/Errors.ts +++ b/cucumber-expressions/javascript/src/Errors.ts @@ -61,6 +61,21 @@ export function createParameterIsNotAllowedInOptional( ) } +export function createOptionalIsNotAllowedInOptional( + node: Node, + expression: string +): CucumberExpressionError { + return new CucumberExpressionError( + message( + node.start, + expression, + pointAtLocated(node), + 'An optional may not contain an other optional', + "If you did not mean to use an optional type you can use '\\(' to escape the the '('. For more complicated expressions consider using a regular expression instead." + ) + ) +} + export function createTheEndOfLIneCanNotBeEscaped( expression: string ): CucumberExpressionError { @@ -96,6 +111,21 @@ export function createMissingEndToken( ) } +export function createAlternationNotAllowedInOptional( + expression: string, + current: Token +) { + return new CucumberExpressionError( + message( + current.start, + expression, + pointAtLocated(current), + 'An alternation can not be used inside an optional', + "You can use '\\/' to escape the the '/'" + ) + ) +} + export function createCantEscaped(expression: string, index: number) { return new CucumberExpressionError( message( @@ -108,13 +138,22 @@ export function createCantEscaped(expression: string, index: number) { ) } -export function createInvalidParameterTypeName(node: Node, expression: string) { +export function createInvalidParameterTypeName(typeName: string) { + return new CucumberExpressionError( + `Illegal character in parameter name {${typeName}}. Parameter names may not contain '{', '}', '(', ')', '\\' or '/'` + ) +} + +export function createInvalidParameterTypeNameInNode( + token: Token, + expression: string +) { return new CucumberExpressionError( message( - node.start, + token.start, expression, - pointAtLocated(node), - "Parameter names may not contain '[]()$.|?*+'", + pointAtLocated(token), + "Parameter names may not contain '{', '}', '(', ')', '\\' or '/'", 'Did you mean to use a regular expression?' ) ) diff --git a/cucumber-expressions/javascript/src/ParameterType.ts b/cucumber-expressions/javascript/src/ParameterType.ts index b165a694d0..d8c639b59f 100644 --- a/cucumber-expressions/javascript/src/ParameterType.ts +++ b/cucumber-expressions/javascript/src/ParameterType.ts @@ -1,4 +1,7 @@ -import { CucumberExpressionError } from './Errors' +import { + createInvalidParameterTypeName, + CucumberExpressionError, +} from './Errors' const ILLEGAL_PARAMETER_NAME_PATTERN = /([[\]()$.|?*+])/ const UNESCAPE_PATTERN = () => /(\\([[$.|?*+\]]))/g @@ -18,9 +21,7 @@ export default class ParameterType { public static checkParameterTypeName(typeName: string) { if (!this.isValidParameterTypeName(typeName)) { - throw new CucumberExpressionError( - `Illegal character in parameter name {${typeName}}. Parameter names may not contain '[]()$.|?*+'` - ) + throw createInvalidParameterTypeName(typeName) } } diff --git a/cucumber-expressions/javascript/test/CustomParameterTypeTest.ts b/cucumber-expressions/javascript/test/CustomParameterTypeTest.ts index 620da82bab..e91544fe6e 100644 --- a/cucumber-expressions/javascript/test/CustomParameterTypeTest.ts +++ b/cucumber-expressions/javascript/test/CustomParameterTypeTest.ts @@ -44,7 +44,7 @@ describe('Custom parameter type', () => { new ParameterType('[string]', /.*/, String, (s) => s, false, true), { message: - "Illegal character in parameter name {[string]}. Parameter names may not contain '[]()$.|?*+'", + "Illegal character in parameter name {[string]}. Parameter names may not contain '{', '}', '(', ')', '\\' or '/'", } ) }) diff --git a/cucumber-expressions/javascript/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/javascript/testdata/ast/optional-containing-escaped-optional.yaml deleted file mode 100644 index f09199a454..0000000000 --- a/cucumber-expressions/javascript/testdata/ast/optional-containing-escaped-optional.yaml +++ /dev/null @@ -1,14 +0,0 @@ -expression: three ((very\) blind) mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ - {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, - {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, - {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, - {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} - ]}, - {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, - {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} - ]} diff --git a/cucumber-expressions/javascript/testdata/ast/optional-containing-nested-optional.yaml b/cucumber-expressions/javascript/testdata/ast/optional-containing-nested-optional.yaml new file mode 100644 index 0000000000..0fdd55d46b --- /dev/null +++ b/cucumber-expressions/javascript/testdata/ast/optional-containing-nested-optional.yaml @@ -0,0 +1,15 @@ +expression: three ((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 25, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 20, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 7, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 8, "end": 12, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 19, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": " "}, + {"type": "TEXT_NODE", "start": 21, "end": 25, "token": "mice"} + ]} diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-in-optional.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-in-optional.yaml new file mode 100644 index 0000000000..b507e27220 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-alternation-in-optional.yaml @@ -0,0 +1,9 @@ +expression: three( brown/black) mice +text: three brown/black mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three( brown/black) mice + ^ + An alternation can not be used inside an optional. + You can use '\/' to escape the the '/' diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-nested-optional.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-nested-optional.yaml new file mode 100644 index 0000000000..017c3be25d --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-nested-optional.yaml @@ -0,0 +1,8 @@ +expression: "(a(b))" +exception: |- + This Cucumber Expression has a problem at column 3: + + (a(b)) + ^-^ + An optional may not contain an other optional. + If you did not mean to use an optional type you can use '\(' to escape the the '('. For more complicated expressions consider using a regular expression instead. diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml new file mode 100644 index 0000000000..d1c89689e9 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml @@ -0,0 +1,10 @@ +expression: |- + {(string)} +text: something +exception: |- + This Cucumber Expression has a problem at column 2: + + {(string)} + ^ + Parameter names may not contain '{', '}', '(', ')', '\' or '/'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml deleted file mode 100644 index 1dd65aa276..0000000000 --- a/cucumber-expressions/javascript/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: |- - {[string]} -text: something -exception: |- - This Cucumber Expression has a problem at column 1: - - {[string]} - ^--------^ - Parameter names may not contain '[]()$.|?*+'. - Did you mean to use a regular expression? diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml new file mode 100644 index 0000000000..e033648972 --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string\} mice +exception: |- + This Cucumber Expression has a problem at column 24: + + three (exceptionally\) {string\} mice + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml new file mode 100644 index 0000000000..7cb9c6d56a --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string} mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (exceptionally\) {string} mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml b/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml new file mode 100644 index 0000000000..029c4e63bd --- /dev/null +++ b/cucumber-expressions/javascript/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml @@ -0,0 +1,8 @@ +expression: three ((exceptionally\) strong) mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three ((exceptionally\) strong) mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/javascript/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/javascript/testdata/expression/matches-alternation-in-optional-as-text.yaml deleted file mode 100644 index 15fe78bf53..0000000000 --- a/cucumber-expressions/javascript/testdata/expression/matches-alternation-in-optional-as-text.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three( brown/black) mice -text: three brown/black mice -expected: |- - [] diff --git a/cucumber-expressions/ruby/testdata/ast/optional-containing-escaped-optional.yaml b/cucumber-expressions/ruby/testdata/ast/optional-containing-escaped-optional.yaml deleted file mode 100644 index f09199a454..0000000000 --- a/cucumber-expressions/ruby/testdata/ast/optional-containing-escaped-optional.yaml +++ /dev/null @@ -1,14 +0,0 @@ -expression: three ((very\) blind) mice -expected: |- - {"type": "EXPRESSION_NODE", "start": 0, "end": 26, "nodes": [ - {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, - {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, - {"type": "OPTIONAL_NODE", "start": 6, "end": 21, "nodes": [ - {"type": "TEXT_NODE", "start": 7, "end": 8, "token": "("}, - {"type": "TEXT_NODE", "start": 8, "end": 14, "token": "very)"}, - {"type": "TEXT_NODE", "start": 14, "end": 15, "token": " "}, - {"type": "TEXT_NODE", "start": 15, "end": 20, "token": "blind"} - ]}, - {"type": "TEXT_NODE", "start": 21, "end": 22, "token": " "}, - {"type": "TEXT_NODE", "start": 22, "end": 26, "token": "mice"} - ]} diff --git a/cucumber-expressions/ruby/testdata/ast/optional-containing-nested-optional.yaml b/cucumber-expressions/ruby/testdata/ast/optional-containing-nested-optional.yaml new file mode 100644 index 0000000000..0fdd55d46b --- /dev/null +++ b/cucumber-expressions/ruby/testdata/ast/optional-containing-nested-optional.yaml @@ -0,0 +1,15 @@ +expression: three ((very) blind) mice +expected: |- + {"type": "EXPRESSION_NODE", "start": 0, "end": 25, "nodes": [ + {"type": "TEXT_NODE", "start": 0, "end": 5, "token": "three"}, + {"type": "TEXT_NODE", "start": 5, "end": 6, "token": " "}, + {"type": "OPTIONAL_NODE", "start": 6, "end": 20, "nodes": [ + {"type": "OPTIONAL_NODE", "start": 7, "end": 13, "nodes": [ + {"type": "TEXT_NODE", "start": 8, "end": 12, "token": "very"} + ]}, + {"type": "TEXT_NODE", "start": 13, "end": 14, "token": " "}, + {"type": "TEXT_NODE", "start": 14, "end": 19, "token": "blind"} + ]}, + {"type": "TEXT_NODE", "start": 20, "end": 21, "token": " "}, + {"type": "TEXT_NODE", "start": 21, "end": 25, "token": "mice"} + ]} diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-in-optional.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-in-optional.yaml new file mode 100644 index 0000000000..b507e27220 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-alternation-in-optional.yaml @@ -0,0 +1,9 @@ +expression: three( brown/black) mice +text: three brown/black mice +exception: |- + This Cucumber Expression has a problem at column 13: + + three( brown/black) mice + ^ + An alternation can not be used inside an optional. + You can use '\/' to escape the the '/' diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-nested-optional.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-nested-optional.yaml new file mode 100644 index 0000000000..017c3be25d --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-nested-optional.yaml @@ -0,0 +1,8 @@ +expression: "(a(b))" +exception: |- + This Cucumber Expression has a problem at column 3: + + (a(b)) + ^-^ + An optional may not contain an other optional. + If you did not mean to use an optional type you can use '\(' to escape the the '('. For more complicated expressions consider using a regular expression instead. diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml new file mode 100644 index 0000000000..d1c89689e9 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml @@ -0,0 +1,10 @@ +expression: |- + {(string)} +text: something +exception: |- + This Cucumber Expression has a problem at column 2: + + {(string)} + ^ + Parameter names may not contain '{', '}', '(', ')', '\' or '/'. + Did you mean to use a regular expression? diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml deleted file mode 100644 index 1dd65aa276..0000000000 --- a/cucumber-expressions/ruby/testdata/expression/does-not-allow-parameter-type-with-left-bracket.yaml +++ /dev/null @@ -1,10 +0,0 @@ -expression: |- - {[string]} -text: something -exception: |- - This Cucumber Expression has a problem at column 1: - - {[string]} - ^--------^ - Parameter names may not contain '[]()$.|?*+'. - Did you mean to use a regular expression? diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml new file mode 100644 index 0000000000..e033648972 --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string\} mice +exception: |- + This Cucumber Expression has a problem at column 24: + + three (exceptionally\) {string\} mice + ^ + The '{' does not have a matching '}'. + If you did not intend to use a parameter you can use '\{' to escape the a parameter diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml new file mode 100644 index 0000000000..7cb9c6d56a --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml @@ -0,0 +1,8 @@ +expression: three (exceptionally\) {string} mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three (exceptionally\) {string} mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml b/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml new file mode 100644 index 0000000000..029c4e63bd --- /dev/null +++ b/cucumber-expressions/ruby/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml @@ -0,0 +1,8 @@ +expression: three ((exceptionally\) strong) mice +exception: |- + This Cucumber Expression has a problem at column 7: + + three ((exceptionally\) strong) mice + ^ + The '(' does not have a matching ')'. + If you did not intend to use optional text you can use '\(' to escape the optional text diff --git a/cucumber-expressions/ruby/testdata/expression/matches-alternation-in-optional-as-text.yaml b/cucumber-expressions/ruby/testdata/expression/matches-alternation-in-optional-as-text.yaml deleted file mode 100644 index 15fe78bf53..0000000000 --- a/cucumber-expressions/ruby/testdata/expression/matches-alternation-in-optional-as-text.yaml +++ /dev/null @@ -1,4 +0,0 @@ -expression: three( brown/black) mice -text: three brown/black mice -expected: |- - [] From e2535345f32bfeb5245015efb0de26c0b2c73bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 10 Dec 2020 10:25:19 +0000 Subject: [PATCH 179/183] Raise errors from blocks --- .../cucumber_expression.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb index e194f1b2e2..8808fedffe 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb @@ -11,8 +11,8 @@ class CucumberExpression def initialize(expression, parameter_type_registry) @expression = expression - @parameter_types = [] @parameter_type_registry = parameter_type_registry + @parameter_types = [] parser = CucumberExpressionParser.new ast = parser.parse(expression) pattern = rewrite_to_regex(ast) @@ -62,9 +62,9 @@ def escape_regex(expression) end def rewrite_optional(node) - assert_no_parameters(node, lambda { |astNode| ParameterIsNotAllowedInOptional.new(astNode, @expression) }) - assert_no_optionals(node, lambda { |astNode| OptionalIsNotAllowedInOptional.new(astNode, @expression) }) - assert_not_empty(node, lambda { |astNode| OptionalMayNotBeEmpty.new(astNode, @expression) }) + assert_no_parameters(node) { |astNode| raise ParameterIsNotAllowedInOptional.new(astNode, @expression) } + assert_no_optionals(node) { |astNode| raise OptionalIsNotAllowedInOptional.new(astNode, @expression) } + assert_not_empty(node) { |astNode| raise OptionalMayNotBeEmpty.new(astNode, @expression) } regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('') "(?:#{regex})?" end @@ -75,7 +75,7 @@ def rewrite_alternation(node) if alternative.nodes.length == 0 raise AlternativeMayNotBeEmpty.new(alternative, @expression) end - assert_not_empty(alternative, lambda {|astNode| AlternativeMayNotExclusivelyContainOptionals.new(astNode, @expression)}) + assert_not_empty(alternative) { |astNode| raise AlternativeMayNotExclusivelyContainOptionals.new(astNode, @expression) } } regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('|') "(?:#{regex})" @@ -104,24 +104,24 @@ def rewrite_expression(node) "^#{regex}$" end - def assert_not_empty(node, create_node_was_not_empty_error) + def assert_not_empty(node, &raise_error) text_nodes = node.nodes.filter { |astNode| NodeType::TEXT == astNode.type } if text_nodes.length == 0 - raise create_node_was_not_empty_error.call(node) + raise_error.call(node) end end - def assert_no_parameters(node, create_node_contained_a_parameter_error) + def assert_no_parameters(node, &raise_error) nodes = node.nodes.filter { |astNode| NodeType::PARAMETER == astNode.type } if nodes.length > 0 - raise create_node_contained_a_parameter_error.call(nodes[0]) + raise_error.call(nodes[0]) end end - def assert_no_optionals(node, create_node_contained_an_optional_error) + def assert_no_optionals(node, &raise_error) nodes = node.nodes.filter { |astNode| NodeType::OPTIONAL == astNode.type } if nodes.length > 0 - raise create_node_contained_an_optional_error.call(nodes[0]) + raise_error.call(nodes[0]) end end end From 3e228e88b15966f54ae245a2df7b8f5a5d820c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 10 Dec 2020 10:27:01 +0000 Subject: [PATCH 180/183] More ruby idioms --- .../cucumber_expression_generator.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb index b161584fe0..d7c4d1835a 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb @@ -59,9 +59,7 @@ def generate_expressions(text) break end - if pos >= text.length - break - end + break if pos >= text.length end expression_template += escape(text.slice(pos..-1)) @@ -85,19 +83,17 @@ def create_parameter_type_matchers(text) end def create_parameter_type_matchers2(parameter_type, text) - result = [] regexps = parameter_type.regexps - regexps.each do |regexp| + regexps.map do |regexp| regexp = Regexp.new("(#{regexp})") - result.push(ParameterTypeMatcher.new(parameter_type, regexp, text, 0)) + ParameterTypeMatcher.new(parameter_type, regexp, text, 0) end - result end def escape(s) s.gsub(/%/, '%%') .gsub(/\(/, '\\(') - .gsub(/\{/, '\\{') + .gsub(/{/, '\\{') .gsub(/\//, '\\/') end end From 81b41ae0473e2ee016657373322226c901d4cc7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 10 Dec 2020 11:15:28 +0000 Subject: [PATCH 181/183] More ruby idioms --- .../cucumber_expression_parser.rb | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb index 807e820c83..e986789470 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb @@ -2,7 +2,6 @@ require 'cucumber/cucumber_expressions/errors' require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer' - module Cucumber module CucumberExpressions class CucumberExpressionParser @@ -80,13 +79,13 @@ def parse(expression) # left-boundary := whitespace | } | ^ # right-boundary := whitespace | { | $ # alternative: = optional | parameter | text - parse_alternation = lambda do |expression, tokens, current| + parse_alternation = lambda do |expr, tokens, current| previous = current - 1 unless looking_at_any(tokens, previous, [TokenType::START_OF_LINE, TokenType::WHITE_SPACE, TokenType::END_PARAMETER]) return 0, nil end - consumed, ast = parse_tokens_until(expression, alternative_parsers, tokens, current, [TokenType::WHITE_SPACE, TokenType::END_OF_LINE, TokenType::BEGIN_PARAMETER]) + consumed, ast = parse_tokens_until(expr, alternative_parsers, tokens, current, [TokenType::WHITE_SPACE, TokenType::END_OF_LINE, TokenType::BEGIN_PARAMETER]) sub_current = current + consumed unless ast.map { |astNode| astNode.type }.include? NodeType::ALTERNATIVE return 0, nil @@ -139,11 +138,9 @@ def parse_between(type, begin_token, end_token, parsers) end def parse_token(expression, parsers, tokens, start_at) - for parser in parsers do + parsers.each do |parser| consumed, ast = parser.call(expression, tokens, start_at) - unless consumed == 0 - return consumed, ast - end + return consumed, ast unless consumed == 0 end # If configured correctly this will never happen raise 'No eligible parsers for ' + tokens @@ -170,12 +167,7 @@ def parse_tokens_until(expression, parsers, tokens, start_at, end_tokens) end def looking_at_any(tokens, at, token_types) - for token_type in token_types - if looking_at(tokens, at, token_type) - return true - end - end - false + token_types.detect { |token_type| looking_at(tokens, at, token_type) } end def looking_at(tokens, at, token) @@ -208,21 +200,19 @@ def split_alternatives(start, _end, alternation) end def create_alternative_nodes(start, _end, separators, alternatives) - nodes = [] - alternatives.each_with_index do |n, i| + alternatives.each_with_index.map do |n, i| if i == 0 right_separator = separators[i] - nodes.push(Node.new(NodeType::ALTERNATIVE, n, nil, start, right_separator.start)) + Node.new(NodeType::ALTERNATIVE, n, nil, start, right_separator.start) elsif i == alternatives.length - 1 left_separator = separators[i - 1] - nodes.push(Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, _end)) + Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, _end) else left_separator = separators[i - 1] right_separator = separators[i] - nodes.push(Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, right_separator.start)) + Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, right_separator.start) end end - nodes end end end From f1037686525fc42b99b33b7b38ea0192d4ded084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 10 Dec 2020 11:19:36 +0000 Subject: [PATCH 182/183] More ruby idioms --- .../cucumber_expressions/cucumber_expression.rb | 12 +++--------- .../cucumber_expression_tokenizer.rb | 12 ++++-------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb index 8808fedffe..2c1807dfd8 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb @@ -106,23 +106,17 @@ def rewrite_expression(node) def assert_not_empty(node, &raise_error) text_nodes = node.nodes.filter { |astNode| NodeType::TEXT == astNode.type } - if text_nodes.length == 0 - raise_error.call(node) - end + raise_error.call(node) if text_nodes.length == 0 end def assert_no_parameters(node, &raise_error) nodes = node.nodes.filter { |astNode| NodeType::PARAMETER == astNode.type } - if nodes.length > 0 - raise_error.call(nodes[0]) - end + raise_error.call(nodes[0]) if nodes.length > 0 end def assert_no_optionals(node, &raise_error) nodes = node.nodes.filter { |astNode| NodeType::OPTIONAL == astNode.type } - if nodes.length > 0 - raise_error.call(nodes[0]) - end + raise_error.call(nodes[0]) if nodes.length > 0 end end end diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb index 3cd3378960..fb65f03ade 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb @@ -28,7 +28,7 @@ def tokenize(expression) current_token_type = token_type_of(codepoint, treat_as_text) treat_as_text = false - if should_create_new_token(previous_token_type, current_token_type) + if should_create_new_token?(previous_token_type, current_token_type) token = convert_buffer_to_token(previous_token_type) previous_token_type = current_token_type @buffer.push(codepoint) @@ -44,13 +44,9 @@ def tokenize(expression) tokens.push(token) end - if treat_as_text - raise TheEndOfLineCannotBeEscaped.new(expression) - end + raise TheEndOfLineCannotBeEscaped.new(expression) if treat_as_text - tokens.push( - Token.new(TokenType::END_OF_LINE, '', codepoints.length, codepoints.length) - ) + tokens.push(Token.new(TokenType::END_OF_LINE, '', codepoints.length, codepoints.length)) tokens end @@ -90,7 +86,7 @@ def token_type_of(codepoint, treat_as_text) ) end - def should_create_new_token(previous_token_type, current_token_type) + def should_create_new_token?(previous_token_type, current_token_type) current_token_type != previous_token_type || (current_token_type != TokenType::WHITE_SPACE && current_token_type != TokenType::TEXT) end From b0bc335ec5870cd8e483f6524b8ef64acd0b49ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Thu, 10 Dec 2020 11:36:18 +0000 Subject: [PATCH 183/183] Attribution. Closes #601. Closes #726. Closes #767. Closes #770. --- cucumber-expressions/CHANGELOG.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/cucumber-expressions/CHANGELOG.md b/cucumber-expressions/CHANGELOG.md index bd0e6a9b7e..6404d2c44c 100644 --- a/cucumber-expressions/CHANGELOG.md +++ b/cucumber-expressions/CHANGELOG.md @@ -7,11 +7,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ---- ## [Unreleased] +This is a major release of Cucumber Expressions. +Cucumber Expressions now has a formal grammar. + +This grammar is implemented in a hand-written recursive-descent parser. +The new grammar and parser handles edge cases better. + +Most existing expressions should still parse in the same way, but you may +come across edge cases where the expressions are parsed differently. + +This work was a heroic effort by @mpkorstanje who has been working on and off +on this for over a year!! + ### Added ### Changed -### Deprecated +* Some expressions that were valid in previous versions may now be invalid +* Some expressions that were invalid in previous versions may now be valid ### Removed @@ -19,6 +32,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +* [Go, Java, JavaScript, Ruby] New handwritten parser, which fixes several long-standing bugs. + ([#601](https://github.com/cucumber/cucumber/issues/601) + [#726](https://github.com/cucumber/cucumber/issues/726) + [#767](https://github.com/cucumber/cucumber/issues/767) + [#770](https://github.com/cucumber/cucumber/issues/770) + [#771](https://github.com/cucumber/cucumber/pull/771) + [mpkorstanje]) * [Go] Support for Go 1.15 ## [10.3.0] - 2020-08-07