diff --git a/gherkin/java/pom.xml b/gherkin/java/pom.xml index 5503df55db..e143d77cda 100644 --- a/gherkin/java/pom.xml +++ b/gherkin/java/pom.xml @@ -41,7 +41,7 @@ io.cucumber messages - [18.0.0,19.0.0) + 19.0.0-SNAPSHOT diff --git a/gherkin/java/src/main/java/io/cucumber/gherkin/GherkinDialectProvider.java b/gherkin/java/src/main/java/io/cucumber/gherkin/GherkinDialectProvider.java index 2ea56aa5b8..0961f648a8 100644 --- a/gherkin/java/src/main/java/io/cucumber/gherkin/GherkinDialectProvider.java +++ b/gherkin/java/src/main/java/io/cucumber/gherkin/GherkinDialectProvider.java @@ -3,7 +3,6 @@ import java.util.Optional; import java.util.Set; -import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; public final class GherkinDialectProvider { diff --git a/gherkin/java/src/main/java/io/cucumber/gherkin/GherkinDocumentBuilder.java b/gherkin/java/src/main/java/io/cucumber/gherkin/GherkinDocumentBuilder.java index 3fd7272754..6df97f5c44 100644 --- a/gherkin/java/src/main/java/io/cucumber/gherkin/GherkinDocumentBuilder.java +++ b/gherkin/java/src/main/java/io/cucumber/gherkin/GherkinDocumentBuilder.java @@ -81,6 +81,8 @@ private Object getTransformedNode(AstNode node) { return new Step( getLocation(stepLine, 0), stepLine.matchedKeyword, + // TODO: do not pass null - fix this in 1741 + null, stepLine.matchedText, node.getSingle(RuleType.DocString, null), node.getSingle(RuleType.DataTable, null), diff --git a/gherkin/java/src/main/java/io/cucumber/gherkin/PickleCompiler.java b/gherkin/java/src/main/java/io/cucumber/gherkin/PickleCompiler.java index 3085e850f6..3282710700 100644 --- a/gherkin/java/src/main/java/io/cucumber/gherkin/PickleCompiler.java +++ b/gherkin/java/src/main/java/io/cucumber/gherkin/PickleCompiler.java @@ -209,6 +209,8 @@ private PickleStep pickleStep(Step step, List variableCells, TableRow argument, astNodeIds, idGenerator.newId(), + // TODO: do not pass null - fix this in 1741 + null, stepText ); } diff --git a/messages/CHANGELOG.md b/messages/CHANGELOG.md index db8fa5d73c..48e16e03bd 100644 --- a/messages/CHANGELOG.md +++ b/messages/CHANGELOG.md @@ -9,8 +9,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +* Expand the messages protocol with keyword types ([#1966](https://github.com/cucumber/common/pull/1966)) + ### Changed +* [Java] the `PickleStep` constructor has changed - it now needs an extra `PickleStepType` argument. +* [Java] the `Step` constructor has changed - it now needs an extra `StepKeywordType` argument. + ### Deprecated ### Removed diff --git a/messages/go/messages.go b/messages/go/messages.go index 22146d61e8..b87e7f8caa 100644 --- a/messages/go/messages.go +++ b/messages/go/messages.go @@ -122,12 +122,13 @@ type Scenario struct { } type Step struct { - Location *Location `json:"location"` - Keyword string `json:"keyword"` - Text string `json:"text"` - DocString *DocString `json:"docString,omitempty"` - DataTable *DataTable `json:"dataTable,omitempty"` - Id string `json:"id"` + Location *Location `json:"location"` + Keyword string `json:"keyword"` + KeywordType StepKeywordType `json:"keywordType,omitempty"` + Text string `json:"text"` + DocString *DocString `json:"docString,omitempty"` + DataTable *DataTable `json:"dataTable,omitempty"` + Id string `json:"id"` } type TableCell struct { @@ -219,6 +220,7 @@ type PickleStep struct { Argument *PickleStepArgument `json:"argument,omitempty"` AstNodeIds []string `json:"astNodeIds"` Id string `json:"id"` + Type PickleStepType `json:"type,omitempty"` Text string `json:"text"` } @@ -379,6 +381,30 @@ func (e AttachmentContentEncoding) String() string { } } +type PickleStepType string + +const ( + PickleStepType_UNKNOWN PickleStepType = "Unknown" + PickleStepType_CONTEXT PickleStepType = "Context" + PickleStepType_ACTION PickleStepType = "Action" + PickleStepType_OUTCOME PickleStepType = "Outcome" +) + +func (e PickleStepType) String() string { + switch e { + case PickleStepType_UNKNOWN: + return "Unknown" + case PickleStepType_CONTEXT: + return "Context" + case PickleStepType_ACTION: + return "Action" + case PickleStepType_OUTCOME: + return "Outcome" + default: + panic("Bad enum value for PickleStepType") + } +} + type SourceMediaType string const ( @@ -415,6 +441,33 @@ func (e StepDefinitionPatternType) String() string { } } +type StepKeywordType string + +const ( + StepKeywordType_UNKNOWN StepKeywordType = "Unknown" + StepKeywordType_CONTEXT StepKeywordType = "Context" + StepKeywordType_ACTION StepKeywordType = "Action" + StepKeywordType_OUTCOME StepKeywordType = "Outcome" + StepKeywordType_CONJUNCTION StepKeywordType = "Conjunction" +) + +func (e StepKeywordType) String() string { + switch e { + case StepKeywordType_UNKNOWN: + return "Unknown" + case StepKeywordType_CONTEXT: + return "Context" + case StepKeywordType_ACTION: + return "Action" + case StepKeywordType_OUTCOME: + return "Outcome" + case StepKeywordType_CONJUNCTION: + return "Conjunction" + default: + panic("Bad enum value for StepKeywordType") + } +} + type TestStepResultStatus string const ( diff --git a/messages/java/pom.xml b/messages/java/pom.xml index b21710a6d1..d7938a6476 100644 --- a/messages/java/pom.xml +++ b/messages/java/pom.xml @@ -8,7 +8,7 @@ 2.3.2 messages - 18.0.1-SNAPSHOT + 19.0.0-SNAPSHOT jar Cucumber Messages JSON schema-based messages for Cucumber's inter-process communication diff --git a/messages/java/src/generated/java/io/cucumber/messages/types/PickleStep.java b/messages/java/src/generated/java/io/cucumber/messages/types/PickleStep.java index 29b7cf8d7c..7430d2f5b3 100644 --- a/messages/java/src/generated/java/io/cucumber/messages/types/PickleStep.java +++ b/messages/java/src/generated/java/io/cucumber/messages/types/PickleStep.java @@ -13,17 +13,20 @@ public final class PickleStep { private final PickleStepArgument argument; private final java.util.List astNodeIds; private final String id; + private final PickleStepType type; private final String text; public PickleStep( PickleStepArgument argument, java.util.List astNodeIds, String id, + PickleStepType type, String text ) { this.argument = argument; this.astNodeIds = unmodifiableList(new ArrayList<>(requireNonNull(astNodeIds, "PickleStep.astNodeIds cannot be null"))); this.id = requireNonNull(id, "PickleStep.id cannot be null"); + this.type = type; this.text = requireNonNull(text, "PickleStep.text cannot be null"); } @@ -39,6 +42,10 @@ public String getId() { return id; } + public Optional getType() { + return Optional.ofNullable(type); + } + public String getText() { return text; } @@ -52,6 +59,7 @@ public boolean equals(Object o) { Objects.equals(argument, that.argument) && astNodeIds.equals(that.astNodeIds) && id.equals(that.id) && + Objects.equals(type, that.type) && text.equals(that.text); } @@ -61,6 +69,7 @@ public int hashCode() { argument, astNodeIds, id, + type, text ); } @@ -71,6 +80,7 @@ public String toString() { "argument=" + argument + ", astNodeIds=" + astNodeIds + ", id=" + id + + ", type=" + type + ", text=" + text + '}'; } diff --git a/messages/java/src/generated/java/io/cucumber/messages/types/PickleStepType.java b/messages/java/src/generated/java/io/cucumber/messages/types/PickleStepType.java new file mode 100644 index 0000000000..739a04b640 --- /dev/null +++ b/messages/java/src/generated/java/io/cucumber/messages/types/PickleStepType.java @@ -0,0 +1,38 @@ +package io.cucumber.messages.types; + +// Generated code +@SuppressWarnings("unused") +public enum PickleStepType { + + UNKNOWN("Unknown"), + + CONTEXT("Context"), + + ACTION("Action"), + + OUTCOME("Outcome"); + + private final String value; + + PickleStepType(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + public String value() { + return this.value; + } + + public static PickleStepType fromValue(String value) { + for (PickleStepType v : values()) { + if (v.value.equals(value)) { + return v; + } + } + throw new IllegalArgumentException(value); + } +} diff --git a/messages/java/src/generated/java/io/cucumber/messages/types/Step.java b/messages/java/src/generated/java/io/cucumber/messages/types/Step.java index ce9d1d6170..032e96b09f 100644 --- a/messages/java/src/generated/java/io/cucumber/messages/types/Step.java +++ b/messages/java/src/generated/java/io/cucumber/messages/types/Step.java @@ -12,6 +12,7 @@ public final class Step { private final Location location; private final String keyword; + private final StepKeywordType keywordType; private final String text; private final DocString docString; private final DataTable dataTable; @@ -20,6 +21,7 @@ public final class Step { public Step( Location location, String keyword, + StepKeywordType keywordType, String text, DocString docString, DataTable dataTable, @@ -27,6 +29,7 @@ public Step( ) { this.location = requireNonNull(location, "Step.location cannot be null"); this.keyword = requireNonNull(keyword, "Step.keyword cannot be null"); + this.keywordType = keywordType; this.text = requireNonNull(text, "Step.text cannot be null"); this.docString = docString; this.dataTable = dataTable; @@ -41,6 +44,10 @@ public String getKeyword() { return keyword; } + public Optional getKeywordType() { + return Optional.ofNullable(keywordType); + } + public String getText() { return text; } @@ -65,6 +72,7 @@ public boolean equals(Object o) { return location.equals(that.location) && keyword.equals(that.keyword) && + Objects.equals(keywordType, that.keywordType) && text.equals(that.text) && Objects.equals(docString, that.docString) && Objects.equals(dataTable, that.dataTable) && @@ -76,6 +84,7 @@ public int hashCode() { return Objects.hash( location, keyword, + keywordType, text, docString, dataTable, @@ -88,6 +97,7 @@ public String toString() { return "Step{" + "location=" + location + ", keyword=" + keyword + + ", keywordType=" + keywordType + ", text=" + text + ", docString=" + docString + ", dataTable=" + dataTable + diff --git a/messages/java/src/generated/java/io/cucumber/messages/types/StepKeywordType.java b/messages/java/src/generated/java/io/cucumber/messages/types/StepKeywordType.java new file mode 100644 index 0000000000..5266048346 --- /dev/null +++ b/messages/java/src/generated/java/io/cucumber/messages/types/StepKeywordType.java @@ -0,0 +1,40 @@ +package io.cucumber.messages.types; + +// Generated code +@SuppressWarnings("unused") +public enum StepKeywordType { + + UNKNOWN("Unknown"), + + CONTEXT("Context"), + + ACTION("Action"), + + OUTCOME("Outcome"), + + CONJUNCTION("Conjunction"); + + private final String value; + + StepKeywordType(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + public String value() { + return this.value; + } + + public static StepKeywordType fromValue(String value) { + for (StepKeywordType v : values()) { + if (v.value.equals(value)) { + return v; + } + } + throw new IllegalArgumentException(value); + } +} diff --git a/messages/javascript/src/messages.ts b/messages/javascript/src/messages.ts index fdfb697aab..8e6903ec3a 100644 --- a/messages/javascript/src/messages.ts +++ b/messages/javascript/src/messages.ts @@ -253,6 +253,8 @@ export class Step { keyword: string = '' + keywordType?: StepKeywordType + text: string = '' @Type(() => DocString) @@ -418,6 +420,8 @@ export class PickleStep { id: string = '' + type?: PickleStepType + text: string = '' } @@ -651,6 +655,13 @@ export enum AttachmentContentEncoding { BASE64 = 'BASE64', } +export enum PickleStepType { + UNKNOWN = 'Unknown', + CONTEXT = 'Context', + ACTION = 'Action', + OUTCOME = 'Outcome', +} + export enum SourceMediaType { TEXT_X_CUCUMBER_GHERKIN_PLAIN = 'text/x.cucumber.gherkin+plain', TEXT_X_CUCUMBER_GHERKIN_MARKDOWN = 'text/x.cucumber.gherkin+markdown', @@ -661,6 +672,14 @@ export enum StepDefinitionPatternType { REGULAR_EXPRESSION = 'REGULAR_EXPRESSION', } +export enum StepKeywordType { + UNKNOWN = 'Unknown', + CONTEXT = 'Context', + ACTION = 'Action', + OUTCOME = 'Outcome', + CONJUNCTION = 'Conjunction', +} + export enum TestStepResultStatus { UNKNOWN = 'UNKNOWN', PASSED = 'PASSED', diff --git a/messages/javascript/test/messagesTest.ts b/messages/javascript/test/messagesTest.ts index ffd80a838b..3a2c6adcdf 100644 --- a/messages/javascript/test/messagesTest.ts +++ b/messages/javascript/test/messagesTest.ts @@ -1,5 +1,5 @@ import assert from 'assert' -import { Envelope, parseEnvelope } from '../src/index.js' +import { Envelope, parseEnvelope, StepKeywordType } from '../src/index.js' describe('messages', () => { it('defaults missing fields when deserialising from JSON', () => { @@ -18,6 +18,7 @@ describe('messages', () => { { id: '0', keyword: 'Given ', + keywordType: StepKeywordType.CONTEXT, location: { column: 5, line: 4 }, text: 'the minimalism', }, @@ -61,6 +62,7 @@ describe('messages', () => { { id: '0', keyword: 'Given ', + keywordType: StepKeywordType.CONTEXT, location: { column: 5, line: 4 }, text: 'the minimalism', }, diff --git a/messages/jsonschema/GherkinDocument.json b/messages/jsonschema/GherkinDocument.json index 70d5f07c3a..911644a6f5 100644 --- a/messages/jsonschema/GherkinDocument.json +++ b/messages/jsonschema/GherkinDocument.json @@ -347,8 +347,20 @@ "description": "The location of the steps' `keyword`" }, "keyword": { + "description": "The actual keyword as it appeared in the source.", "type": "string" }, + "keywordType": { + "description": "The test phase signalled by the keyword: Context definition (Given), Action performance (When), Outcome assertion (Then). Other keywords signal Continuation (And and But) from a prior keyword. Please note that all translations which a dialect maps to multiple keywords (`*` is in this category for all dialects), map to 'Unknown'.", + "type": "string", + "enum": [ + "Unknown", + "Context", + "Action", + "Outcome", + "Conjunction" + ] + }, "text": { "type": "string" }, diff --git a/messages/jsonschema/Pickle.json b/messages/jsonschema/Pickle.json index f6fb6befa7..10877afeb9 100644 --- a/messages/jsonschema/Pickle.json +++ b/messages/jsonschema/Pickle.json @@ -43,6 +43,16 @@ "description": "A unique ID for the PickleStep", "type": "string" }, + "type": { + "description": "The context in which the step was specified: context (Given), action (When) or outcome (Then).\n\nNote that the keywords `But` and `And` inherit their meaning from prior steps and the `*` 'keyword' doesn't have specific meaning (hence Unknown)", + "type": "string", + "enum": [ + "Unknown", + "Context", + "Action", + "Outcome" + ] + }, "text": { "type": "string" } diff --git a/messages/jsonschema/scripts/templates/perl.pm.erb b/messages/jsonschema/scripts/templates/perl.pm.erb index b5fa275832..7ce6e452a6 100644 --- a/messages/jsonschema/scripts/templates/perl.pm.erb +++ b/messages/jsonschema/scripts/templates/perl.pm.erb @@ -104,11 +104,11 @@ Available constants for valid values of this field: <% if property['enum'] -%> -use constant +use constant { <%- property['enum'].to_a.each_with_index do |value, index| -%> <%= property_name.upcase %>_<%= enum_constant(value) %> => '<%= value %>', <%- end -%> - ; + }; <% end -%> has <%= underscore(property_name) %> => diff --git a/messages/messages.md b/messages/messages.md index 1f2b425aa1..4389076682 100644 --- a/messages/messages.md +++ b/messages/messages.md @@ -158,6 +158,7 @@ will only have one of its fields set, which indicates the payload of the message | ----- | ---- | ----------- | ----------- | | `location` | [Location](#location) | yes | | | `keyword` | string | yes | | +| `keywordType` | [StepKeywordType](#stepkeywordtype) | no | | | `text` | string | yes | | | `docString` | [DocString](#docstring) | no | | | `dataTable` | [DataTable](#datatable) | no | | @@ -281,6 +282,7 @@ will only have one of its fields set, which indicates the payload of the message | `argument` | [PickleStepArgument](#picklestepargument) | no | | | `astNodeIds` | string[] | yes | | | `id` | string | yes | | +| `type` | [PickleStepType](#picklesteptype) | no | | | `text` | string | yes | | ## PickleStepArgument @@ -480,6 +482,16 @@ One of the following: * `"BASE64"` +## PickleStepType + +One of the following: + +* `"Unknown"` +* `"Context"` +* `"Action"` +* `"Outcome"` + + ## SourceMediaType One of the following: @@ -496,6 +508,17 @@ One of the following: * `"REGULAR_EXPRESSION"` +## StepKeywordType + +One of the following: + +* `"Unknown"` +* `"Context"` +* `"Action"` +* `"Outcome"` +* `"Conjunction"` + + ## TestStepResultStatus One of the following: diff --git a/messages/perl/lib/Cucumber/Messages.pm b/messages/perl/lib/Cucumber/Messages.pm index 5505519c8e..7d733ea7fc 100644 --- a/messages/perl/lib/Cucumber/Messages.pm +++ b/messages/perl/lib/Cucumber/Messages.pm @@ -138,10 +138,10 @@ Available constants for valid values of this field: =cut -use constant +use constant { CONTENTENCODING_IDENTITY => 'IDENTITY', CONTENTENCODING_BASE64 => 'BASE64', - ; + }; has content_encoding => (is => 'ro', @@ -1594,6 +1594,7 @@ use Scalar::Util qw( blessed ); my %types = ( location => 'Cucumber::Messages::Location', keyword => 'string', + keyword_type => '', text => 'string', doc_string => 'Cucumber::Messages::DocString', data_table => 'Cucumber::Messages::DataTable', @@ -1623,6 +1624,7 @@ has location => =head4 keyword +The actual keyword as it appeared in the source. =cut @@ -1633,6 +1635,43 @@ has keyword => ); +=head4 keyword_type + +The test phase signalled by the keyword: Context definition (Given), Action performance (When), Outcome assertion (Then). Other keywords signal Continuation (And and But) from a prior keyword. Please note that all translations which a dialect maps to multiple keywords (`*` is in this category for all dialects), map to 'Unknown'. + + +Available constants for valid values of this field: + +=over + +=item * KEYWORDTYPE_UNKNOWN + +=item * KEYWORDTYPE_CONTEXT + +=item * KEYWORDTYPE_ACTION + +=item * KEYWORDTYPE_OUTCOME + +=item * KEYWORDTYPE_CONJUNCTION + +=back + +=cut + + +use constant { + KEYWORDTYPE_UNKNOWN => 'Unknown', + KEYWORDTYPE_CONTEXT => 'Context', + KEYWORDTYPE_ACTION => 'Action', + KEYWORDTYPE_OUTCOME => 'Outcome', + KEYWORDTYPE_CONJUNCTION => 'Conjunction', + }; + +has keyword_type => + (is => 'ro', + ); + + =head4 text @@ -2759,6 +2798,7 @@ my %types = ( argument => 'Cucumber::Messages::PickleStepArgument', ast_node_ids => '[]string', id => 'string', + type => '', text => 'string', ); @@ -2807,6 +2847,42 @@ has id => ); +=head4 type + +The context in which the step was specified: context (Given), action (When) or outcome (Then). + +Note that the keywords `But` and `And` inherit their meaning from prior steps and the `*` 'keyword' doesn't have specific meaning (hence Unknown) + + +Available constants for valid values of this field: + +=over + +=item * TYPE_UNKNOWN + +=item * TYPE_CONTEXT + +=item * TYPE_ACTION + +=item * TYPE_OUTCOME + +=back + +=cut + + +use constant { + TYPE_UNKNOWN => 'Unknown', + TYPE_CONTEXT => 'Context', + TYPE_ACTION => 'Action', + TYPE_OUTCOME => 'Outcome', + }; + +has type => + (is => 'ro', + ); + + =head4 text @@ -3159,10 +3235,10 @@ Available constants for valid values of this field: =cut -use constant +use constant { MEDIATYPE_TEXT_X_CUCUMBER_GHERKIN_PLAIN => 'text/x.cucumber.gherkin+plain', MEDIATYPE_TEXT_X_CUCUMBER_GHERKIN_MARKDOWN => 'text/x.cucumber.gherkin+markdown', - ; + }; has media_type => (is => 'ro', @@ -3530,10 +3606,10 @@ Available constants for valid values of this field: =cut -use constant +use constant { TYPE_CUCUMBER_EXPRESSION => 'CUCUMBER_EXPRESSION', TYPE_REGULAR_EXPRESSION => 'REGULAR_EXPRESSION', - ; + }; has type => (is => 'ro', @@ -4352,7 +4428,7 @@ Available constants for valid values of this field: =cut -use constant +use constant { STATUS_UNKNOWN => 'UNKNOWN', STATUS_PASSED => 'PASSED', STATUS_SKIPPED => 'SKIPPED', @@ -4360,7 +4436,7 @@ use constant STATUS_UNDEFINED => 'UNDEFINED', STATUS_AMBIGUOUS => 'AMBIGUOUS', STATUS_FAILED => 'FAILED', - ; + }; has status => (is => 'ro', diff --git a/messages/php/src-generated/PickleStep.php b/messages/php/src-generated/PickleStep.php index 6d8bc287ee..d361be6596 100644 --- a/messages/php/src-generated/PickleStep.php +++ b/messages/php/src-generated/PickleStep.php @@ -38,6 +38,13 @@ public function __construct( * A unique ID for the PickleStep */ public readonly string $id = '', + + /** + * The context in which the step was specified: context (Given), action (When) or outcome (Then). + * + * Note that the keywords `But` and `And` inherit their meaning from prior steps and the `*` 'keyword' doesn't have specific meaning (hence Unknown) + */ + public readonly ?PickleStep\Type $type = null, public readonly string $text = '', ) { } @@ -52,12 +59,14 @@ public static function fromArray(array $arr): self self::ensureArgument($arr); self::ensureAstNodeIds($arr); self::ensureId($arr); + self::ensureType($arr); self::ensureText($arr); return new self( isset($arr['argument']) ? PickleStepArgument::fromArray($arr['argument']) : null, array_values(array_map(fn (mixed $member) => (string) $member, $arr['astNodeIds'])), (string) $arr['id'], + isset($arr['type']) ? PickleStep\Type::from((string) $arr['type']) : null, (string) $arr['text'], ); } @@ -98,6 +107,16 @@ private static function ensureId(array $arr): void } } + /** + * @psalm-assert array{type?: string|int|bool} $arr + */ + private static function ensureType(array $arr): void + { + if (array_key_exists('type', $arr) && is_array($arr['type'])) { + throw new SchemaViolationException('Property \'type\' was array'); + } + } + /** * @psalm-assert array{text: string|int|bool} $arr */ diff --git a/messages/php/src-generated/PickleStep/Type.php b/messages/php/src-generated/PickleStep/Type.php new file mode 100644 index 0000000000..1221cbc03e --- /dev/null +++ b/messages/php/src-generated/PickleStep/Type.php @@ -0,0 +1,17 @@ +