Top

Validation

Static analysis or validation is one of the most interesting aspects when developing a programming language. The users of your languages will be grateful if they get informative feedback as they type. In Xtext there are basically three different kinds of validation.

Automatic Validation

Some implementation aspects (e.g. the grammar, scoping) of a language have an impact on what is required for a document or semantic model to be valid. Xtext automatically takes care of this.

Lexer/Parser: Syntactical Validation

The syntactical correctness of any textual input is validated automatically by the parser. The error messages are generated by the underlying parser technology. One can use the ISyntaxErrorMessageProvider (src)-API to customize this messages. Any syntax errors can be retrieved from the Resource using the common EMF API:

Linker: Crosslink Validation

Any broken crosslinks can be checked generically. As crosslink resolution is done lazily (see linking), any broken links are resolved lazily as well. If you want to validate whether all links are valid, you will have to navigate through the model so that all installed EMF proxies get resolved. This is done automatically in the editor.

Similar to syntax errors, any unresolvable crosslinks will be reported and can be obtained through:

Serializer: Concrete Syntax Validation

The IConcreteSyntaxValidator (src) validates all constraints that are implied by a grammar. Meeting these constraints for a model is mandatory to be serialized.

Example:

MyRule:
  ({MySubRule} "sub")? (strVal+=ID intVal+=INT)*;

This implies several constraints:

  1. Types: only instances of MyRule and MySubRule are allowed for this rule. Subtypes are prohibited, since the parser never instantiates unknown subtypes.
  2. Features: In case the MyRule and MySubRule have EStructuralFeatures besides strVal and intVal, only strVal and intVal may have non-transient values.
  3. Quantities: The following condition must be true: strVal.size() == intVal.size().
  4. Values: It must be possible to convert all values to valid tokens for terminal rule STRING. The same is true for intVal and INT.

The typical use case for the concrete syntax validator is validation in non-Xtext-editors that, however, use an XtextResource (src). This is, for example, the case when combining GMF and Xtext. Another use case is when the semantic model is modified "manually" (not by the parser) and then serialized again. Since it is very difficult for the serializer to provide meaningful error messages, the concrete syntax validator is executed by default before serialization. A textual Xtext editor itself is not a valid use case. Here, the parser ensures that all syntactical constraints are met. Therefore, there is no value in additionally running the concrete syntax validator.

There are some limitations to the concrete syntax validator which result from the fact that it treats the grammar as declarative, which is something the parser doesn't always do.

  • Grammar rules containing assigned actions (e.g. {MyType.myFeature=current} are ignored. Unassigned actions (e.g. {MyType}), however, are supported.
  • Grammar rules that delegate to one or more rules containing assigned actions via unassigned rule calls are ignored.
  • Orders within list-features can not be validated. e.g. Rule: (foo+=R1 foo+=R2)* implies that foo is expected to contain instances of R1 and R2 in an alternating order.

To use concrete syntax validation you can let Guice inject an instance of IConcreteSyntaxValidator (src) and use it directly. Furthermore, there is an adapter (src) which allows to use the concrete syntax validator as an EValidator. You can, for example, enable it in your runtime module, by adding:

@SingletonBinding(eager = true)
public Class<? extends ConcreteSyntaxEValidator> 
      bindConcreteSyntaxEValidator() {
  return ConcreteSyntaxEValidator.class;
}

To customize error messages please see IConcreteSyntaxDiagnosticProvider (src) and subclass ConcreteSyntaxDiagnosticProvider (src).

Custom Validation

In addition to the afore mentioned kinds of validation, which are more or less done automatically, you can specify additional constraints specific for your Ecore model. We leverage existing EMF API and have put some convenience stuff on top. Basically all you need to do is to make sure that an EValidator is registered for your EPackage. The EValidator.Registry can only be filled programmatically. That means contrary to the EPackage.Registry and the Resource.Factory.Registry there is no Equinox extension point to populate the validator registry.

For Xtext we provide a generator fragment for the convenient Java-based EValidator API. Just add the following fragment to your generator configuration and you are good to go:

fragment = 
    org.eclipse.xtext.generator.validation.JavaValidatorFragment {}

The generator will provide you with two Java classes. An abstract class generated to src-gen/ which extends the library class AbstractDeclarativeValidator (src). This one just registers the EPackages for which this validator introduces constraints. The other class is a subclass of that abstract class and is generated to the src/ folder in order to be edited by you. That is where you put the constraints in.

The purpose of the AbstractDeclarativeValidator (src) is to allow you to write constraints in a declarative way - as the class name already suggests. That is instead of writing exhaustive if-else constructs or extending the generated EMF switch you just have to add the @Check (src) annotation to any method and it will be invoked automatically when validation takes place. Moreover you can state for what type the respective constraint method is, just by declaring a typed parameter. This also lets you avoid any type casts. In addition to the reflective invocation of validation methods the AbstractDeclarativeValidator (src) provides a couple of convenient assertions.

All in all this is very similar to how JUnit 4 works. Here is an example:

public class DomainmodelJavaValidator 
  extends AbstractDomainmodelJavaValidator {
    
  @Check
  public void checkTypeNameStartsWithCapital(Type type) {
    if (!Character.isUpperCase(type.getName().charAt(0)))
      warning("Name should start with a capital"
        DomainmodelPackage.TYPE__NAME);
  }
}

You can also implement quick fixes for individual validation errors and warnings. See the chapter on quick fixes for details.

Validating Manually

As noted above, Xtext uses EMF's EValidator API to register validators. You can run the validators on your model programmatically using EMF's Diagnostician, e.g.

EObject myModel = myResource.getContents().get(0);
Diagnostic diagnostic = Diagnostician.INSTANCE.validate(myModel);
switch (diagnostic.getSeverity()) {
  case Diagnostic.ERROR:
    System.err.println("Model has errors: ",diagnostic);
    break;
  case Diagnostic.WARNING:
    System.err.println("Model has warnings: ",diagnostic);
}

Test Validators

If you have implemented your validators by extending AbstractDeclarativeValidator (src), there are helper classes which assist you when testing your validators.

Testing validators typically works as follows:

  1. The test creates some models which intentionally violate some constraints.
  2. The test runs some chosen @Check-methods (src) from the validator.
  3. The test asserts whether the @Check-methods (src) have raised the expected warnings and errors.

To create models, you can either use EMF's ResourceSet to load models from your hard disk or you can utilize the MyDslFactory that EMF generates for each EPackage, to construct the tested model elements manually. While the first option has the advantages that you can edit your models in your textual concrete syntax, the second option has the advantage that you can create partial models.

To run the @Check-methods (src) and ensure they raise the intended errors and warnings, you can utilize ValidatorTester (src) as shown by the following example:

Validator:

public class MyLanguageValidator extends AbstractDeclarativeValidator {
  @Check
  public void checkFooElement(FooElement element) {
    if(element.getBarAttribute().contains("foo"))
      error("Only Foos allowed", element, 
        MyLanguagePackage.FOO_ELEMENT__BAR_ATTRIBUTE, 101);
  }
}

JUnit-Test:

public class MyLanguageValidatorTest extends AbstractXtextTests {

  private ValidatorTester<MyLanguageValidator> tester;

  @Override
  public void setUp() {
    with(MyLanguageStandaloneSetup.class);
    MyLanguageValidator validator = get(MyLanguageValidator.class);
    tester = new ValidatorTester<TestingValidator>(validator);
  }

  public void testError() {
    FooElement model = MyLanguageFactory.eINSTANCE.createFooElement()
    model.setBarAttribute("barbarbarbarfoo");
    
    tester.validator().checkFooElement(model);
    tester.diagnose().assertError(101);
  }
  
  public void testError2() {
    FooElement model = MyLanguageFactory.eINSTANCE.createFooElement()
    model.setBarAttribute("barbarbarbarfoo");
    
    tester.validate(model).assertError(101);
  }
}

This example uses JUnit 3, but since the involved classes from Xtext have no dependency on JUnit whatsoever, JUnit 4 and other testing frameworks will work as well. JUnit runs the setUp()-method before each test case and thereby helps to create some common state. In this example, the validator is instantiated by means of Google Guice. As we inherit from the AbstractXtextTests (src) there are a plenty of useful methods available and the state of the global EMF singletons will be restored in the method tearDown(). Afterwards, the ValidatorTester (src) is created and parameterized with the actual validator. It acts as a wrapper for the validator, ensures that the validator has a valid state and provides convenient access to the validator itself (tester.validator()) as well as to the utility classes which assert diagnostics created by the validator (tester.diagnose()). Please be aware that you have to call validator() before you can call diagnose(). However, you can call validator() multiple times in a row.

While validator() allows to call the validator's @Check-methods (src) directly, validate(model) leaves it to the framework to call the applicable @Check-methods (src). However, to avoid side-effects between tests, it is recommended to call the @Check-methods (src) directly.

diagnose() and validate(model) return an object of type AssertableDiagnostics (src) which provides several assert-methods to verify whether the expected diagnostics are present:

  • assertError(int code): There must be one diagnostic with severity ERROR and the supplied error code.
  • assertErrorContains(String messageFragment): There must be one diagnostic with severity ERROR and its message must contain messageFragment.
  • assertError(int code, String messageFragment): Verifies severity, error code and messageFragment.
  • assertWarning(...): This method is available for the same combination of parameters as assertError().
  • assertOK(): Expects that no diagnostics (errors, warnings etc.) have been raised.
  • assertDiagnostics(int severity, int code, String messageFragment): Verifies severity, error code and messageFragment.
  • assertAll(DiagnosticPredicate... predicates): Allows to describe multiple diagnostics at the same time and verifies that all of them are present. Class AssertableDiagnostics (src) contains static error() and warning() methods which help to create the needed AssertableDiagnostics.DiagnosticPredicate (src). Example: assertAll(error(123), warning("some part of the message")).
  • assertAny(DiagnosticPredicate predicate): Asserts that a diagnostic exists which matches the predicate.