diff --git a/examples/java-datatable/src/main/java/cucumber/examples/datatable/AnimalValidator.java b/examples/java-datatable/src/main/java/cucumber/examples/datatable/AnimalValidator.java
new file mode 100644
index 00000000..acb2fb9a
--- /dev/null
+++ b/examples/java-datatable/src/main/java/cucumber/examples/datatable/AnimalValidator.java
@@ -0,0 +1,78 @@
+package cucumber.examples.datatable;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import io.cucumber.core.gherkin.DataTableArgument;
+import io.cucumber.plugin.ConcurrentEventListener;
+import io.cucumber.plugin.Plugin;
+import io.cucumber.plugin.event.EventPublisher;
+import io.cucumber.plugin.event.PickleStepTestStep;
+import io.cucumber.plugin.event.Step;
+import io.cucumber.plugin.event.StepArgument;
+import io.cucumber.plugin.event.TestStep;
+import io.cucumber.plugin.event.TestStepFinished;
+
+/**
+ * This validator is enabled in the feature by using
+ * #validation-plugin: cucumber.examples.datatable.AnimalValidator
+ */
+public class AnimalValidator implements Plugin, ConcurrentEventListener {
+
+ private ConcurrentHashMap errors = new ConcurrentHashMap<>();
+
+ @Override
+ public void setEventPublisher(EventPublisher publisher) {
+ publisher.registerHandlerFor(TestStepFinished.class, this::handleTestStepFinished);
+ }
+
+ private void handleTestStepFinished(TestStepFinished event) {
+ TestStep testStep = event.getTestStep();
+ if (testStep instanceof PickleStepTestStep) {
+ PickleStepTestStep pickleStepTestStep = (PickleStepTestStep) testStep;
+ Step step = pickleStepTestStep.getStep();
+ if ("the animal {string}".equals(pickleStepTestStep.getPattern())) {
+ StepArgument argument = step.getArgument();
+ Animals animal = loadAnimal(pickleStepTestStep.getDefinitionArgument().get(0).getValue());
+ if (animal == null) {
+ // Invalid animal!
+ return;
+ }
+ if (argument instanceof DataTableArgument dataTable) {
+ List availableData = animal.getAvailableData();
+ List> cells = dataTable.cells();
+ for (int i = 1; i < cells.size(); i++) {
+ int line = dataTable.getLine() + i;
+ List list = cells.get(i);
+ String vv = list.get(0);
+ if (!animal.getAvailableDataForAnimals().contains(vv)) {
+ errors.put(line, vv + " is not valid for any animal");
+ } else if (!availableData.contains(vv)) {
+ errors.put(line, vv + " is not valid for animal " + animal.getClass().getSimpleName());
+ }
+ }
+ }
+ }
+ }
+ }
+ //This is a magic method called by cucumber-eclipse to fetch the final errors and display them in the document
+ public Map getValidationErrors() {
+ return errors;
+ }
+
+ private Animals loadAnimal(String value) {
+ try {
+ Class> clz = getClass().getClassLoader()
+ .loadClass("cucumber.examples.datatable." + value.replace("\"", ""));
+ Object instance = clz.getConstructor().newInstance();
+ if (instance instanceof Animals anml) {
+ return anml;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+}
diff --git a/examples/java-datatable/src/test/resources/cucumber/examples/datatable.feature b/examples/java-datatable/src/test/resources/cucumber/examples/datatable.feature
index f1205aff..8b93a104 100644
--- a/examples/java-datatable/src/test/resources/cucumber/examples/datatable.feature
+++ b/examples/java-datatable/src/test/resources/cucumber/examples/datatable.feature
@@ -1,4 +1,6 @@
#language: en
+#This comment below enables the validation plugin
+#validation-plugin: cucumber.examples.datatable.AnimalValidator
Feature: Connection between DataTable Key and a specific Step Value
diff --git a/io.cucumber.eclipse.editor/plugin.xml b/io.cucumber.eclipse.editor/plugin.xml
index 5598bb71..cb437a59 100644
--- a/io.cucumber.eclipse.editor/plugin.xml
+++ b/io.cucumber.eclipse.editor/plugin.xml
@@ -110,6 +110,14 @@
+
+
+
+
+
errors, boolean persistent) {
+ if (errors == null || errors.isEmpty()) {
+ return;
+ }
+
+ mark(resource, new IMarkerBuilder() {
+ @Override
+ public void build() throws CoreException {
+ IMarker[] markers = resource.findMarkers(STEPDEF_VALIDATION_ERROR, true, IResource.DEPTH_INFINITE);
+ for (IMarker marker : markers) {
+ marker.delete();
+ }
+ for (Entry entry : errors.entrySet()) {
+ IMarker marker = resource.createMarker(STEPDEF_VALIDATION_ERROR);
+ marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
+ marker.setAttribute(IMarker.MESSAGE, entry.getValue());
+ marker.setAttribute(IMarker.LINE_NUMBER, entry.getKey());
+ marker.setAttribute(IMarker.TRANSIENT, persistent);
+ }
+ }
+ });
+
+ }
+
public void syntaxErrorOnStepDefinition(IResource stepDefinitionResource, Exception e) {
syntaxErrorOnStepDefinition(stepDefinitionResource, e, 0);
}
diff --git a/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/runtime/CucumberRuntime.java b/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/runtime/CucumberRuntime.java
index 56497eae..a83c4fae 100644
--- a/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/runtime/CucumberRuntime.java
+++ b/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/runtime/CucumberRuntime.java
@@ -150,6 +150,23 @@ public void addPlugin(Plugin plugin) {
plugins.add(plugin);
}
+ public Plugin addPluginFromClasspath(String clazz) {
+ try {
+ Class> c = classLoader.loadClass(clazz);
+ Object instance = c.getConstructor().newInstance();
+ if (instance instanceof Plugin) {
+ Plugin plugin = (Plugin) instance;
+ addPlugin(plugin);
+ return plugin;
+ }
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+// plugins.add(plugin);
+ }
+
public void addFeature(GherkinEditorDocument document) {
IResource resource = document.getResource();
URI uri = Objects.requireNonNullElseGet(resource.getLocationURI(), () -> resource.getRawLocationURI());
diff --git a/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/validation/CucumberGlueValidator.java b/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/validation/CucumberGlueValidator.java
index 1f64a7ef..cbf3d15d 100644
--- a/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/validation/CucumberGlueValidator.java
+++ b/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/validation/CucumberGlueValidator.java
@@ -2,8 +2,12 @@
import static io.cucumber.eclipse.editor.Tracing.PERFORMANCE_STEPS;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -23,9 +27,11 @@
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IRegion;
import org.eclipse.osgi.service.debug.DebugTrace;
import io.cucumber.core.gherkin.FeatureParserException;
@@ -40,6 +46,7 @@
import io.cucumber.eclipse.java.plugins.CucumberStepParserPlugin;
import io.cucumber.eclipse.java.plugins.MatchedStep;
import io.cucumber.eclipse.java.runtime.CucumberRuntime;
+import io.cucumber.plugin.Plugin;
/**
* Performs a dry-run on the document to verify step definition matching
@@ -252,9 +259,15 @@ protected IStatus run(IProgressMonitor monitor) {
rt.addPlugin(stepParserPlugin);
rt.addPlugin(matchedStepsPlugin);
rt.addPlugin(missingStepsPlugin);
+ Collection validationPlugins = addValidationPlugins(editorDocument, rt);
try {
rt.run(monitor);
+ Map validationErrors = new HashMap<>();
+ for (Plugin plugin : validationPlugins) {
+ addErrors(plugin, validationErrors);
+ }
Map> snippets = missingStepsPlugin.getSnippets();
+ MarkerFactory.validationErrorOnStepDefinition(resource, validationErrors, persistent);
MarkerFactory.missingSteps(resource, snippets, Activator.PLUGIN_ID, persistent);
Collection steps = stepParserPlugin.getStepList();
matchedSteps = Collections.unmodifiableCollection(matchedStepsPlugin.getMatchedSteps());
@@ -295,6 +308,46 @@ protected IStatus run(IProgressMonitor monitor) {
return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
}
+ private Collection addValidationPlugins(GherkinEditorDocument editorDocument, CucumberRuntime rt) {
+ List validationPlugins = new ArrayList<>();
+ IDocument doc = editorDocument.getDocument();
+ int lines = doc.getNumberOfLines();
+ for (int i = 0; i < lines; i++) {
+ try {
+ IRegion firstLine = document.getLineInformation(i);
+ String line = document.get(firstLine.getOffset(), firstLine.getLength()).trim();
+ if (line.startsWith("#")) {
+ String[] split = line.split("validation-plugin:", 2);
+ if (split.length == 2) {
+ String validationPlugin = split[1].trim();
+ Plugin classpathPlugin = rt.addPluginFromClasspath(validationPlugin);
+ if (classpathPlugin != null) {
+ validationPlugins.add(classpathPlugin);
+ }
+ }
+ }
+ } catch (BadLocationException e) {
+ }
+ }
+ return validationPlugins;
+ }
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void addErrors(Plugin plugin, Map validationErrors) {
+ try {
+ Method method = plugin.getClass().getMethod("getValidationErrors");
+ Object invoke = method.invoke(plugin);
+ if (invoke instanceof Map) {
+ @SuppressWarnings("rawtypes")
+ Map map = (Map) invoke;
+ validationErrors.putAll(map);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
}
}