or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.cucumber/cucumber-expressions@19.0.x

docs

index.md
tile.json

tessl/maven-io-cucumber--cucumber-expressions

tessl install tessl/maven-io-cucumber--cucumber-expressions@19.0.0

Cucumber Expressions are simple patterns for matching Step Definitions with Gherkin steps

parsing.mddocs/reference/

Parsing (Experimental)

The parsing API provides AST-based (Abstract Syntax Tree) parsing for Cucumber Expressions, enabling advanced expression analysis, manipulation, and tooling. This API is experimental and may change in future versions.

Note: This API is marked as @API(status = EXPERIMENTAL) since version 18.1 and is subject to change.

Thread Safety

  • CucumberExpressionParser: Stateless and thread-safe
  • Node instances: Immutable value objects, thread-safe

Capabilities

CucumberExpressionParser

Parse Cucumber Expressions into an Abstract Syntax Tree representation.

package io.cucumber.cucumberexpressions;

import org.apiguardian.api.API;

/**
 * Parse cucumber expressions into AST
 * Experimental API - subject to change
 * Stateless parser - thread-safe
 */
@API(since = "18.1", status = API.Status.EXPERIMENTAL)
public final class CucumberExpressionParser {
    /**
     * Create a new parser
     * No configuration required
     */
    public CucumberExpressionParser();
    
    /**
     * Parse an expression into an AST
     * Thread-safe operation - stateless parser
     * @param expression - Cucumber Expression string to parse
     * @return Root Node representing the expression structure
     */
    public Node parse(String expression);
}

Usage Examples:

import io.cucumber.cucumberexpressions.*;

// Create parser
CucumberExpressionParser parser = new CucumberExpressionParser();

// Parse simple expression
Node ast = parser.parse("I have {int} cucumbers");

// Analyze AST structure
System.out.println("AST: " + ast.toString());
// JSON representation of the AST

// Access root node properties
Node.Type type = ast.type();
// type = Node.Type.EXPRESSION_NODE

List<Node> children = ast.nodes();
// Children contain text nodes and parameter nodes

Complex Expression:

// Parse expression with optional and alternative text
String expr = "I have {int} cucumber(s) in my belly/stomach";
Node ast = parser.parse(expr);

// Traverse AST
for (Node child : ast.nodes()) {
    switch (child.type()) {
        case TEXT_NODE:
            System.out.println("Text: " + child.token());
            break;
        case PARAMETER_NODE:
            System.out.println("Parameter: " + child.token());
            break;
        case OPTIONAL_NODE:
            System.out.println("Optional: " + child.token());
            break;
        case ALTERNATION_NODE:
            System.out.println("Alternation with " + child.nodes().size() + " alternatives");
            break;
    }
}

Node Class

AST node representing parsed expression structure with position information.

package io.cucumber.cucumberexpressions;

import org.apiguardian.api.API;
import org.jspecify.annotations.Nullable;
import java.util.List;

/**
 * AST node representing parsed expression structure
 * Immutable value object with position information
 * Experimental API - subject to change
 */
@API(since = "18.1", status = API.Status.EXPERIMENTAL)
public final class Node implements Located {
    /**
     * Node type enumeration
     */
    public enum Type {
        /** Plain text node */
        TEXT_NODE,
        /** Optional text node (parentheses) */
        OPTIONAL_NODE,
        /** Alternation container node */
        ALTERNATION_NODE,
        /** Single alternative within alternation */
        ALTERNATIVE_NODE,
        /** Parameter type reference node */
        PARAMETER_NODE,
        /** Root expression node */
        EXPRESSION_NODE
    }
    
    /**
     * Get start position in source expression
     * @return Zero-based start index (inclusive)
     */
    public int start();
    
    /**
     * Get end position in source expression
     * End position is exclusive
     * @return Zero-based end index (exclusive)
     */
    public int end();
    
    /**
     * Get child nodes
     * Null for leaf nodes (TEXT_NODE, PARAMETER_NODE)
     * @return List of child nodes, or null for leaf nodes
     */
    @Nullable
    public List<Node> nodes();
    
    /**
     * Get node type
     * @return Node type enum value
     */
    public Node.Type type();
    
    /**
     * Get token text
     * Contains text for TEXT_NODE and OPTIONAL_NODE
     * Returns null for container nodes (EXPRESSION_NODE, ALTERNATION_NODE, ALTERNATIVE_NODE)
     * Returns null for PARAMETER_NODE (use position and source text to extract parameter type name)
     * @return Token text or null
     */
    @Nullable
    public String token();
    
    /**
     * JSON representation of the AST
     * @return JSON string representation
     */
    public String toString();
    
    /**
     * Equality check
     * @param o - Object to compare
     * @return true if equal
     */
    public boolean equals(Object o);
    
    /**
     * Hash code
     * @return Hash code value
     */
    public int hashCode();
}

Located Interface

Marker interface for types with position information.

package io.cucumber.cucumberexpressions;

/**
 * Marker interface for types with position information
 */
public interface Located {
    /**
     * Get start position
     * @return Zero-based start index (inclusive)
     */
    int start();
    
    /**
     * Get end position
     * @return Zero-based end index (exclusive)
     */
    int end();
}

Node Types

EXPRESSION_NODE:

Root node of the AST, contains all top-level elements.

Node ast = parser.parse("I have {int} cucumbers");
assert ast.type() == Node.Type.EXPRESSION_NODE;
assert ast.token() == null;  // Container node
assert ast.nodes() != null;  // Has children

TEXT_NODE:

Plain text content.

Node ast = parser.parse("I have {int} cucumbers");
Node firstChild = ast.nodes().get(0);
assert firstChild.type() == Node.Type.TEXT_NODE;
assert firstChild.token().equals("I have ");
assert firstChild.nodes() == null;  // Leaf node

PARAMETER_NODE:

Parameter type reference like {int} or {color}.

Note: token() returns null for PARAMETER_NODE types. To identify the parameter type name, extract it from the source expression using the node's position information.

String expression = "I have {int} cucumbers";
Node ast = parser.parse(expression);
Node paramNode = ast.nodes().get(1);
assert paramNode.type() == Node.Type.PARAMETER_NODE;
assert paramNode.token() == null;  // token() returns null for parameter nodes
assert paramNode.nodes() == null;  // Leaf node

// To get the parameter type name, extract from source using position:
int start = paramNode.start();
int end = paramNode.end();
String parameterText = expression.substring(start, end);
// parameterText = "{int}"

// To get just the type name without braces:
String typeName = parameterText.substring(1, parameterText.length() - 1);
// typeName = "int"

OPTIONAL_NODE:

Optional text enclosed in parentheses.

Node ast = parser.parse("I have {int} cucumber(s)");
// Find optional node
Node optionalNode = findNodeByType(ast, Node.Type.OPTIONAL_NODE);
assert optionalNode.token().equals("s");

ALTERNATION_NODE:

Container for alternative text options.

Node ast = parser.parse("I have {int} in my belly/stomach");
// Find alternation node
Node alternationNode = findNodeByType(ast, Node.Type.ALTERNATION_NODE);
assert alternationNode.nodes() != null;
assert alternationNode.nodes().size() == 2;  // Two alternatives

ALTERNATIVE_NODE:

Single alternative within an alternation.

Node ast = parser.parse("I have {int} in my belly/stomach");
Node alternationNode = findNodeByType(ast, Node.Type.ALTERNATION_NODE);
Node alt1 = alternationNode.nodes().get(0);
Node alt2 = alternationNode.nodes().get(1);
assert alt1.type() == Node.Type.ALTERNATIVE_NODE;
assert alt2.type() == Node.Type.ALTERNATIVE_NODE;

AST Traversal

Recursive Traversal:

import io.cucumber.cucumberexpressions.*;
import java.util.List;

public class ASTVisitor {
    public void visit(Node node, int depth) {
        String indent = "  ".repeat(depth);
        System.out.println(indent + node.type() +
            (node.token() != null ? ": " + node.token() : ""));
        
        if (node.nodes() != null) {
            for (Node child : node.nodes()) {
                visit(child, depth + 1);
            }
        }
    }
    
    public static void main(String[] args) {
        CucumberExpressionParser parser = new CucumberExpressionParser();
        Node ast = parser.parse("I have {int} cucumber(s) in my belly/stomach");
        
        ASTVisitor visitor = new ASTVisitor();
        visitor.visit(ast, 0);
    }
}

// Output:
// EXPRESSION_NODE
//   TEXT_NODE: I have
//   PARAMETER_NODE
//   TEXT_NODE:  cucumber
//   OPTIONAL_NODE: s
//   TEXT_NODE:  in my
//   ALTERNATION_NODE
//     ALTERNATIVE_NODE
//       TEXT_NODE:  belly
//     ALTERNATIVE_NODE
//       TEXT_NODE:  stomach

Collecting Specific Nodes:

public List<Node> findNodesByType(Node root, Node.Type targetType) {
    List<Node> results = new ArrayList<>();
    collectNodesByType(root, targetType, results);
    return results;
}

private void collectNodesByType(Node node, Node.Type targetType, List<Node> results) {
    if (node.type() == targetType) {
        results.add(node);
    }
    if (node.nodes() != null) {
        for (Node child : node.nodes()) {
            collectNodesByType(child, targetType, results);
        }
    }
}

// Usage
Node ast = parser.parse("I have {int} items and {string} name");
List<Node> paramNodes = findNodesByType(ast, Node.Type.PARAMETER_NODE);
// paramNodes contains nodes for "int" and "string"

Position Information

Use position information for error reporting, highlighting, or transformation:

String expression = "I have {int} cucumbers";
Node ast = parser.parse(expression);

// Find parameter node
List<Node> params = findNodesByType(ast, Node.Type.PARAMETER_NODE);
Node paramNode = params.get(0);

// Get position
int start = paramNode.start();
int end = paramNode.end();

// Extract from source
String paramText = expression.substring(start, end);
// paramText = "{int}"

// Highlight in source
String before = expression.substring(0, start);
String after = expression.substring(end);
String highlighted = before + "[" + paramText + "]" + after;
// highlighted = "I have [{int}] cucumbers"

AST Analysis Use Cases

Extract All Parameters:

public List<String> extractParameterTypes(String expression) {
    CucumberExpressionParser parser = new CucumberExpressionParser();
    Node ast = parser.parse(expression);
    
    List<String> paramTypes = new ArrayList<>();
    collectParameterTypes(ast, expression, paramTypes);
    return paramTypes;
}

private void collectParameterTypes(Node node, String expression, List<String> types) {
    if (node.type() == Node.Type.PARAMETER_NODE) {
        // Extract parameter type name from source using position
        String paramText = expression.substring(node.start(), node.end());
        // Remove braces to get type name: "{int}" -> "int"
        String typeName = paramText.substring(1, paramText.length() - 1);
        types.add(typeName);
    }
    if (node.nodes() != null) {
        for (Node child : node.nodes()) {
            collectParameterTypes(child, expression, types);
        }
    }
}

// Usage
List<String> types = extractParameterTypes("User {string} has {int} items");
// types = ["string", "int"]

Validate Expression Structure:

public List<String> validateExpression(String expression) {
    List<String> errors = new ArrayList<>();
    
    try {
        CucumberExpressionParser parser = new CucumberExpressionParser();
        Node ast = parser.parse(expression);
        
        // Check for empty parameters
        List<Node> params = findNodesByType(ast, Node.Type.PARAMETER_NODE);
        for (Node param : params) {
            // Extract parameter text from source using position
            String paramText = expression.substring(param.start(), param.end());
            // Check if empty: "{}"
            if (paramText.equals("{}")) {
                errors.add("Anonymous parameter at position " + param.start());
            }
        }
        
        // Check for nested optional/alternation (if not allowed)
        validateNoNestedOptionals(ast, errors);
        
    } catch (Exception e) {
        errors.add("Parse error: " + e.getMessage());
    }
    
    return errors;
}

Transform Expression:

public String replaceParameters(String expression, String targetType) {
    CucumberExpressionParser parser = new CucumberExpressionParser();
    Node ast = parser.parse(expression);
    
    StringBuilder result = new StringBuilder();
    int lastEnd = 0;
    
    List<Node> params = findNodesByType(ast, Node.Type.PARAMETER_NODE);
    for (Node param : params) {
        // Copy text before parameter
        result.append(expression, lastEnd, param.start());
        // Replace parameter
        result.append("{").append(targetType).append("}");
        lastEnd = param.end();
    }
    // Copy remaining text
    result.append(expression.substring(lastEnd));
    
    return result.toString();
}

// Usage
String transformed = replaceParameters("I have {int} and {float}", "number");
// transformed = "I have {number} and {number}"

JSON Representation

The toString() method returns a JSON representation of the AST:

CucumberExpressionParser parser = new CucumberExpressionParser();
Node ast = parser.parse("I have {int} cucumbers");

String json = ast.toString();
System.out.println(json);

// Output (formatted):
// {
//   "type": "EXPRESSION_NODE",
//   "start": 0,
//   "end": 21,
//   "nodes": [
//     {
//       "type": "TEXT_NODE",
//       "start": 0,
//       "end": 7,
//       "token": "I have "
//     },
//     {
//       "type": "PARAMETER_NODE",
//       "start": 7,
//       "end": 12,
//       "token": null
//     },
//     {
//       "type": "TEXT_NODE",
//       "start": 12,
//       "end": 21,
//       "token": " cucumbers"
//     }
//   ]
// }
// Note: token is null for PARAMETER_NODE - use start/end to extract "{int}" from source

Experimental API Notice

Important Considerations:

  1. API Stability: This API is experimental and may change in future versions
  2. Semantic Versioning: Changes to experimental APIs don't require major version bumps
  3. Production Use: Consider stability requirements before using in production
  4. Migration: Be prepared to update code when the API stabilizes
  5. Feedback: Report issues and suggestions to help shape the final API

Checking API Status:

// The experimental annotation is part of the API Guardian annotations
import org.apiguardian.api.API;

// Check class annotation
API annotation = CucumberExpressionParser.class.getAnnotation(API.class);
if (annotation != null) {
    System.out.println("Status: " + annotation.status());
    System.out.println("Since: " + annotation.since());
}
// Status: EXPERIMENTAL
// Since: 18.1

Advanced Use Cases

IDE Integration:

  • Syntax highlighting based on node types
  • Code completion for parameter types
  • Error highlighting at specific positions

Code Generation:

  • Generate step definitions from expressions
  • Create test data based on parameter types
  • Generate documentation from expressions

Expression Analysis:

  • Find similar expressions
  • Suggest expression simplifications
  • Detect potential conflicts

Expression Transformation:

  • Refactor parameter type names
  • Convert between expression formats
  • Normalize expression style

Related Documentation

  • Expressions - Create and use expressions
  • Parameter Types - Define parameter types
  • Expression Generation - Generate expressions from text