Package com.puppetlabs.geppetto.pp.dsl.validation

Source Code of com.puppetlabs.geppetto.pp.dsl.validation.PPJavaValidator$CollectChecker

/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*   Puppet Labs
*/
package com.puppetlabs.geppetto.pp.dsl.validation;

import static com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter.RESOURCE_IS_BAD;
import static com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter.RESOURCE_IS_CLASSPARAMS;
import static com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter.RESOURCE_IS_DEFAULT;
import static com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter.RESOURCE_IS_OVERRIDE;
import static com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter.RESOURCE_IS_REGULAR;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;

import com.puppetlabs.geppetto.pp.AdditiveExpression;
import com.puppetlabs.geppetto.pp.AndExpression;
import com.puppetlabs.geppetto.pp.AppendExpression;
import com.puppetlabs.geppetto.pp.AssignmentExpression;
import com.puppetlabs.geppetto.pp.AtExpression;
import com.puppetlabs.geppetto.pp.AttributeOperation;
import com.puppetlabs.geppetto.pp.AttributeOperations;
import com.puppetlabs.geppetto.pp.BinaryExpression;
import com.puppetlabs.geppetto.pp.BinaryOpExpression;
import com.puppetlabs.geppetto.pp.Case;
import com.puppetlabs.geppetto.pp.CaseExpression;
import com.puppetlabs.geppetto.pp.CollectExpression;
import com.puppetlabs.geppetto.pp.Definition;
import com.puppetlabs.geppetto.pp.DefinitionArgument;
import com.puppetlabs.geppetto.pp.DefinitionArgumentList;
import com.puppetlabs.geppetto.pp.DoubleQuotedString;
import com.puppetlabs.geppetto.pp.ElseExpression;
import com.puppetlabs.geppetto.pp.ElseIfExpression;
import com.puppetlabs.geppetto.pp.EqualityExpression;
import com.puppetlabs.geppetto.pp.Expression;
import com.puppetlabs.geppetto.pp.ExpressionTE;
import com.puppetlabs.geppetto.pp.FunctionCall;
import com.puppetlabs.geppetto.pp.HostClassDefinition;
import com.puppetlabs.geppetto.pp.IQuotedString;
import com.puppetlabs.geppetto.pp.IfExpression;
import com.puppetlabs.geppetto.pp.ImportExpression;
import com.puppetlabs.geppetto.pp.InExpression;
import com.puppetlabs.geppetto.pp.Lambda;
import com.puppetlabs.geppetto.pp.LiteralBoolean;
import com.puppetlabs.geppetto.pp.LiteralDefault;
import com.puppetlabs.geppetto.pp.LiteralHash;
import com.puppetlabs.geppetto.pp.LiteralList;
import com.puppetlabs.geppetto.pp.LiteralName;
import com.puppetlabs.geppetto.pp.LiteralNameOrReference;
import com.puppetlabs.geppetto.pp.LiteralRegex;
import com.puppetlabs.geppetto.pp.LiteralUndef;
import com.puppetlabs.geppetto.pp.MatchingExpression;
import com.puppetlabs.geppetto.pp.MethodCall;
import com.puppetlabs.geppetto.pp.MultiplicativeExpression;
import com.puppetlabs.geppetto.pp.NodeDefinition;
import com.puppetlabs.geppetto.pp.OrExpression;
import com.puppetlabs.geppetto.pp.PPPackage;
import com.puppetlabs.geppetto.pp.ParenthesisedExpression;
import com.puppetlabs.geppetto.pp.PuppetManifest;
import com.puppetlabs.geppetto.pp.RelationalExpression;
import com.puppetlabs.geppetto.pp.RelationshipExpression;
import com.puppetlabs.geppetto.pp.ResourceBody;
import com.puppetlabs.geppetto.pp.ResourceExpression;
import com.puppetlabs.geppetto.pp.SelectorEntry;
import com.puppetlabs.geppetto.pp.SelectorExpression;
import com.puppetlabs.geppetto.pp.SeparatorExpression;
import com.puppetlabs.geppetto.pp.ShiftExpression;
import com.puppetlabs.geppetto.pp.SingleQuotedString;
import com.puppetlabs.geppetto.pp.StringExpression;
import com.puppetlabs.geppetto.pp.TextExpression;
import com.puppetlabs.geppetto.pp.UnaryExpression;
import com.puppetlabs.geppetto.pp.UnaryMinusExpression;
import com.puppetlabs.geppetto.pp.UnaryNotExpression;
import com.puppetlabs.geppetto.pp.UnlessExpression;
import com.puppetlabs.geppetto.pp.UnquotedString;
import com.puppetlabs.geppetto.pp.VariableExpression;
import com.puppetlabs.geppetto.pp.VariableTE;
import com.puppetlabs.geppetto.pp.VerbatimTE;
import com.puppetlabs.geppetto.pp.VirtualNameOrReference;
import com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter;
import com.puppetlabs.geppetto.pp.adapters.ClassifierAdapterFactory;
import com.puppetlabs.geppetto.pp.dsl.eval.PPExpressionEquivalenceCalculator;
import com.puppetlabs.geppetto.pp.dsl.eval.PPStringConstantEvaluator;
import com.puppetlabs.geppetto.pp.dsl.eval.PPTypeEvaluator;
import com.puppetlabs.geppetto.pp.dsl.linking.IMessageAcceptor;
import com.puppetlabs.geppetto.pp.dsl.linking.PPClassifier;
import com.puppetlabs.geppetto.pp.dsl.linking.ValidationBasedMessageAcceptor;
import com.puppetlabs.geppetto.pp.dsl.services.PPGrammarAccess;
import com.puppetlabs.geppetto.pp.util.TextExpressionHelper;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.IGrammarAccess;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.impl.LeafNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.util.Exceptions;
import org.eclipse.xtext.util.PolymorphicDispatcher;
import org.eclipse.xtext.util.PolymorphicDispatcher.ErrorHandler;
import org.eclipse.xtext.validation.Check;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class PPJavaValidator extends AbstractPPJavaValidator implements IPPDiagnostics {
  /**
   * The CollectChecker is used to check the validity of puppet CollectExpression
   */
  protected class CollectChecker {
    private final PolymorphicDispatcher<Void> collectCheckerDispatch = new PolymorphicDispatcher<Void>(
      "check", 1, 2, Collections.singletonList(this), new ErrorHandler<Void>() {
        public Void handle(Object[] params, Throwable e) {
          return handleError(params, e);
        }
      });

    public void check(AndExpression o) {
      doCheck(o.getLeftExpr());
      doCheck(o.getRightExpr());
    }

    public void check(AtExpression o, boolean left) {
      if(left)
        check(o);
    }

    public void check(EqualityExpression o) {
      doCheck(o.getLeftExpr(), Boolean.TRUE);
      doCheck(o.getRightExpr(), Boolean.FALSE);
    }

    public void check(EqualityExpression o, boolean left) {
      check(o);
    }

    public void check(Expression o) {
      acceptor.acceptError(
        "Expression type not allowed here.", o.eContainer(), o.eContainingFeature(), INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    }

    public void check(Expression o, boolean left) {
      acceptor.acceptError(
        "Expression type not allowed as " + (left
            ? "left"
            : "right") + " expression.", o.eContainer(), o.eContainingFeature(), INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    }

    public void check(LiteralBoolean o, boolean left) {
      if(left)
        check(o);
    }

    public void check(LiteralName o, boolean left) {
      // intrinsic check of LiteralName is enough
    }

    public void check(LiteralNameOrReference o, boolean left) {
      if(isNAME(o.getValue()))
        return; // ok both left and right
      if(!left && isCLASSREF(o.getValue())) // accept "type" if right
        return;

      acceptor.acceptError("Must be a name" + (!left
          ? " or type."
          : "."), o, PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE, INSIGNIFICANT_INDEX, left
          ? IPPDiagnostics.ISSUE__NOT_NAME
          : IPPDiagnostics.ISSUE__NOT_NAME_OR_REF);
    }

    public void check(OrExpression o) {
      doCheck(o.getLeftExpr());
      doCheck(o.getRightExpr());
    }

    public void check(ParenthesisedExpression o) {
      doCheck(o.getExpr());
    }

    public void check(SelectorExpression o, boolean left) {
      if(left)
        check(o);
    }

    public void check(StringExpression o, boolean left) {
      // TODO: follow up if all types of StringExpression (single, double, and unquoted are supported).
      if(left)
        check(o);
    }

    public void check(VariableExpression o, boolean left) {
      // intrinsic variable check is enough
    }

    /**
     * Calls "collectCheck" in polymorphic fashion.
     *
     * @param o
     */
    public void doCheck(Object... o) {
      collectCheckerDispatch.invoke(o);
    }

    public Void handleError(Object[] params, Throwable e) {
      return Exceptions.throwUncheckedException(e);
    }
  }

  /**
   * Classifies ResourceExpression based on its content (regular, override, etc).
   */
  @Inject
  private PPClassifier classifier;

  final protected IMessageAcceptor acceptor;

  @Inject
  private PPPatternHelper patternHelper;

  @Inject
  private PPStringConstantEvaluator stringConstantEvaluator;

  private final Provider<IValidationAdvisor> validationAdvisorProvider;

  @Inject
  private PPExpressionEquivalenceCalculator eqCalculator;

  /**
   * Classes accepted as top level statements in a pp manifest.
   */
  private static final Class<?>[] topLevelExprClasses = { ResourceExpression.class, // resource, virtual, resource override
      RelationshipExpression.class, //
      CollectExpression.class, //
      AssignmentExpression.class, //
      IfExpression.class, //
      UnlessExpression.class, //
      CaseExpression.class, //
      ImportExpression.class, //
      FunctionCall.class, //
      MethodCall.class, //
      Definition.class, HostClassDefinition.class, //
      NodeDefinition.class, //
      AppendExpression.class, //
      SeparatorExpression.class //
  };

  /**
   * Classes accepted as RVALUE.
   */
  private static final Class<?>[] rvalueClasses = { StringExpression.class, // TODO: was LiteralString, follow up
      LiteralNameOrReference.class, // NAME and TYPE
      LiteralBoolean.class, //
      SelectorExpression.class, //
      VariableExpression.class, //
      LiteralList.class, //
      LiteralHash.class, //
      AtExpression.class, // HashArray access or ResourceReference are accepted
      // resource reference - see AtExpression
      FunctionCall.class, // i.e. only parenthesized form
      MethodCall.class, LiteralUndef.class, };

  private static final Class<?>[] expressionClasses = { InExpression.class, //
      MatchingExpression.class, //
      AdditiveExpression.class, //
      MultiplicativeExpression.class, //
      ShiftExpression.class, //
      UnaryMinusExpression.class, //
      UnaryNotExpression.class, //
      RelationalExpression.class, //
      EqualityExpression.class, //
      OrExpression.class, //
      AndExpression.class, };

  private static final String[] keywords = {
      "and", "or", "case", "default", "define", "import", "if", "elsif", "else", "inherits", "node", "in",
      "undef", "true", "false" };

  @Inject
  protected PPExpressionTypeNameProvider expressionTypeNameProvider;

  @Inject
  protected PPTypeEvaluator typeEvaluator;

  @Inject
  private PPGrammarAccess grammarAccess;

  private final ImmutableSet<EClass> extendedRelationshipClasses = ImmutableSet.of(
    PPPackage.Literals.VARIABLE_EXPRESSION, //
    PPPackage.Literals.DOUBLE_QUOTED_STRING, //
    PPPackage.Literals.SINGLE_QUOTED_STRING, //
    PPPackage.Literals.LITERAL_HASH, //
    PPPackage.Literals.LITERAL_LIST, //
    PPPackage.Literals.SELECTOR_EXPRESSION, //
    PPPackage.Literals.CASE_EXPRESSION, //
    PPPackage.Literals.COLLECT_EXPRESSION
  // ,
  );

  @Inject
  public PPJavaValidator(IGrammarAccess ga, Provider<IValidationAdvisor> validationAdvisorProvider) {
    acceptor = new ValidationBasedMessageAcceptor(this);
    this.validationAdvisorProvider = validationAdvisorProvider;
  }

  private IValidationAdvisor advisor() {
    return validationAdvisorProvider.get();
  }

  @Check
  public void checkAdditiveExpression(AdditiveExpression o) {
    checkOperator(o, "+", "-");
    checkNumericBinaryExpression(o);
  }

  @Check
  public void checkAppendExpression(AppendExpression o) {
    Expression leftExpr = o.getLeftExpr();
    if(!(leftExpr instanceof VariableExpression))
      acceptor.acceptError(
        "Not an appendable expression", o, PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_APPENDABLE);
    else
      checkAssignability(o, (VariableExpression) leftExpr);

  }

  /**
   * Common functionality for $x = ... and $x += ...
   *
   * @param o
   *            the binary expr where varExpr is the leftExpr
   * @param varExpr
   *            the leftExpr of the given o BinaryExpression
   */
  private void checkAssignability(BinaryExpression o, VariableExpression varExpr) {

    // Variables in 'other namespaces' are not assignable.
    if(varExpr.getVarName().contains("::"))
      acceptor.acceptError("Cannot assign to variables in other namespaces", o, //
        PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX, //
        IPPDiagnostics.ISSUE__ASSIGNMENT_OTHER_NAMESPACE);

    // Decimal numeric variables are not assignable (clash with magic regexp variables)
    if(patternHelper.isDECIMALVAR(varExpr.getVarName()))
      acceptor.acceptError("Cannot assign to regular expression result variable", o, //
        PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX, //
        IPPDiagnostics.ISSUE__ASSIGNMENT_DECIMAL_VAR);

    // The name "$string" causes inline templates to produce the wrong content.
    // http://projects.puppetlabs.com/issues/14093
    ValidationPreference preference = advisor().assignmentToVarNamedString();
    if(preference.isWarningOrError() &&
        ("string".equals(varExpr.getVarName()) || "$string".equals(varExpr.getVarName()))) {
      warningOrError(
        acceptor, preference, "Assignment to $string will cause inline templates to fail", varExpr,
        IPPDiagnostics.ISSUE__ASSIGNMENT_TO_VAR_NAMED_STRING);
    }

  }

  @Check
  public void checkAssignmentExpression(AssignmentExpression o) {
    Expression leftExpr = o.getLeftExpr();
    if(!(leftExpr instanceof VariableExpression || leftExpr instanceof AtExpression))
      acceptor.acceptError(
        "Not an assignable expression", o, PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_ASSIGNABLE);
    if(leftExpr instanceof VariableExpression)
      checkAssignability(o, (VariableExpression) leftExpr);

    // TODO: rhs is not validated, it allows expression, which includes rvalue, but some top level expressions
    // are probably not allowed (case?)
  }

  /**
   * Checks if an AtExpression is either a valid hash access, or a resource reference.
   * Use isStandardAtExpression to check if an AtExpression is one or the other.
   * Also see {@link #isStandardAtExpression(AtExpression)})
   *
   * @param o
   */
  @Check
  public void checkAtExpression(AtExpression o) {
    if(!isStandardAtExpression(o)) {
      checkAtExpressionAsResourceReference(o);
      return;
    }

    // Puppet grammar At expression is VARIABLE[expr]([expr])? (i.e. only one nested level).
    //
    final Expression leftExpr = o.getLeftExpr();
    if(leftExpr == null)
      acceptor.acceptError(
        "Expression left of [] is required", o, PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__REQUIRED_EXPRESSION);
    else if(!(leftExpr instanceof VariableExpression || (leftExpr instanceof LiteralNameOrReference && isFirstNameInTE((LiteralNameOrReference) leftExpr)))) {
      // then, the leftExpression *must* be an AtExpression with a leftExpr being a variable
      if(leftExpr instanceof AtExpression) {
        // older versions limited access to two levels.
        if(!advisor().allowMoreThan2AtInSequence()) {
          final Expression nestedLeftExpr = ((AtExpression) leftExpr).getLeftExpr();
          // if nestedLeftExpr is null, it is validated for the nested instance
          if(nestedLeftExpr != null &&
              !(nestedLeftExpr instanceof VariableExpression || (nestedLeftExpr instanceof LiteralNameOrReference && isFirstNameInTE((LiteralNameOrReference) nestedLeftExpr))))
            acceptor.acceptError(
              "Expression left of [] must be a variable.", nestedLeftExpr,
              PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX,
              IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
        }
      }
      else {
        acceptor.acceptError(
          "Expression left of [] must be a variable.", leftExpr,
          PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX,
          IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
      }
    }
    // -- check that there is exactly one parameter expression (the key)
    switch(o.getParameters().size()) {
      case 0:
        acceptor.acceptError(
          "Key/index expression is required", o, PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS,
          INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__REQUIRED_EXPRESSION);
        break;
      case 1:
        break; // ok
      default:
        acceptor.acceptError(
          "Multiple expressions are not allowed", o, PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS,
          INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    }
    // // TODO: Check for valid expressions (don't know if any expression is acceptable)
    // for(Expression expr : o.getParameters()) {
    // //
    // }
  }

  /**
   * Checks that an AtExpression that acts as a ResourceReference is valid.
   *
   * @param resourceRef
   * @param errorStartText
   */
  public void checkAtExpressionAsResourceReference(AtExpression resourceRef) {
    final String errorStartText = "A resource reference";
    Expression leftExpr = resourceRef.getLeftExpr();
    if(leftExpr == null)
      acceptor.acceptError(
        errorStartText + " must start with a name or referece (was null).", resourceRef,
        PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__RESOURCE_REFERENCE_NO_REFERENCE);
    else {
      String name = leftExpr instanceof LiteralNameOrReference
          ? ((LiteralNameOrReference) leftExpr).getValue()
          : null;
      boolean isref = isCLASSREF(name);
      boolean isname = isNAME(name);
      if(!isref) {
        if(!isname)
          acceptor.acceptError(
            errorStartText + " must start with a [(deprecated) name, or] class reference.", resourceRef,
            PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX,
            IPPDiagnostics.ISSUE__NOT_CLASSREF);
        else
          acceptor.acceptWarning(
            errorStartText + " uses deprecated form of reference. Should start with upper case letter.",
            resourceRef, PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX,
            IPPDiagnostics.ISSUE__DEPRECATED_REFERENCE);
      }

    }
    if(resourceRef.getParameters().size() < 1)
      acceptor.acceptError(
        errorStartText + " must have at least one expression in list.", resourceRef,
        PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__RESOURCE_REFERENCE_NO_PARAMETERS);

    // TODO: Possibly check valid expressions in the list, there are probably many illegal constructs valid in Puppet grammar
    // TODO: Handle all relaxations in the puppet model/grammar
    for(Expression expr : resourceRef.getParameters()) {
      if(expr instanceof LiteralRegex)
        acceptor.acceptError(
          errorStartText + " invalid resource reference parameter expression type.", resourceRef,
          PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, resourceRef.getParameters().indexOf(expr),
          IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    }
  }

  @Check
  public void checkAttributeAddition(AttributeOperation o) {
    if(!isNAME(o.getKey()))
      acceptor.acceptError(
        "Bad name format.", o, PPPackage.Literals.ATTRIBUTE_OPERATION__KEY, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__NOT_NAME);
    String op = o.getOp();
    if(!(op != null && (op.equals("=>") || op.equals("+>"))))
      acceptor.acceptError(
        "Bad operator.", o, PPPackage.Literals.ATTRIBUTE_OPERATION__OP, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__UNSUPPORTED_OPERATOR);
    if(o.getValue() == null)
      acceptor.acceptError(
        "Missing value expression", o, PPPackage.Literals.ATTRIBUTE_OPERATION__VALUE, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__NULL_EXPRESSION);
  }

  @Check
  public void checkAttributeOperations(AttributeOperations o) {
    final int count = o.getAttributes().size();
    EList<AttributeOperation> attrs = o.getAttributes();
    for(int i = 0; i < count - 1; i++) {
      INode n = NodeModelUtils.getNode(attrs.get(i));
      INode n2 = NodeModelUtils.getNode(attrs.get(i + 1));

      INode commaNode = null;
      for(commaNode = n.getNextSibling(); commaNode != null; commaNode = commaNode.getNextSibling())
        if(commaNode == n2)
          break;
        else if(commaNode instanceof LeafNode && ((LeafNode) commaNode).isHidden())
          continue;
        else
          break;

      if(commaNode == null || !",".equals(commaNode.getText())) {
        int expectOffset = n.getTotalOffset() + n.getTotalLength();
        acceptor.acceptError("Missing comma.", n.getSemanticElement(),
        // note that offset must be -1 as this ofter a hidden newline and this
        // does not work otherwise. Any quickfix needs to adjust the offset on replacement.
          expectOffset - 1, 2, IPPDiagnostics.ISSUE__MISSING_COMMA);
      }
    }
    // check for duplicate use of attribute/parameter
    Set<String> duplicates = Sets.newHashSet();
    Set<String> processed = Sets.newHashSet();
    AttributeOperations aos = o;

    // find duplicates
    for(AttributeOperation ao : aos.getAttributes()) {
      final String key = ao.getKey();
      if(processed.contains(key))
        duplicates.add(key);
      processed.add(key);
    }
    // mark all instances of duplicate name
    if(duplicates.size() > 0)
      for(AttributeOperation ao : aos.getAttributes())
        if(duplicates.contains(ao.getKey()))
          acceptor.acceptError(
            "Duplicate attribute: '" + ao.getKey() + "'", ao, PPPackage.Literals.ATTRIBUTE_OPERATION__KEY,
            IPPDiagnostics.ISSUE__RESOURCE_DUPLICATE_PARAMETER);
  }

  @Check
  public void checkBinaryExpression(BinaryExpression o) {
    if(o.getLeftExpr() == null)
      acceptor.acceptError(
        "A binary expression must have a left expr", o, PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NULL_EXPRESSION);
    if(o.getRightExpr() == null)
      acceptor.acceptError(
        "A binary expression must have a right expr", o, PPPackage.Literals.BINARY_EXPRESSION__RIGHT_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NULL_EXPRESSION);
  }

  /**
   * Checks case literals that puppet will barf on:
   * - an unquoted text sequence that contains "."
   *
   * @param o
   */
  @Check
  public void checkCase(Case o) {
    internalCheckTopLevelExpressions(o.getStatements());

    ValidationPreference periodInCase = validationAdvisorProvider.get().periodInCase();
    if(periodInCase != ValidationPreference.IGNORE) {
      for(Expression value : o.getValues()) {
        if(value.eClass() == PPPackage.Literals.LITERAL_NAME_OR_REFERENCE) {
          String v = ((LiteralNameOrReference) value).getValue();
          if(v != null && v.contains(".")) {
            if(periodInCase == ValidationPreference.ERROR)
              acceptor.acceptError(
                "A case value containing '.' (period) must be quoted", value,
                IPPDiagnostics.ISSUE__REQUIRES_QUOTING);
            else
              acceptor.acceptWarning(
                "A case value containing '.' (period) should be quoted", value,
                IPPDiagnostics.ISSUE__REQUIRES_QUOTING);
          }
        }
      }
    }
  }

  @Check
  public void checkCaseExpression(CaseExpression o) {
    final Expression switchExpr = o.getSwitchExpr();

    boolean theDefaultIsSeen = false;
    // collect unreachable entries to avoid multiple unreachable markers for an entry
    Set<Integer> unreachables = Sets.newHashSet();
    Set<Integer> duplicates = Sets.newHashSet();
    List<Expression> caseExpressions = Lists.newArrayList();
    for(Case caze : o.getCases()) {
      for(Expression e : caze.getValues()) {
        caseExpressions.add(e);

        if(e instanceof LiteralDefault)
          theDefaultIsSeen = true;
      }
    }

    // if a default is seen it should (optionally) appear last
    if(theDefaultIsSeen) {
      IValidationAdvisor advisor = advisor();
      ValidationPreference shouldBeLast = advisor.caseDefaultShouldAppearLast();
      if(shouldBeLast.isWarningOrError()) {
        int last = caseExpressions.size() - 1;
        for(int i = 0; i < last; i++)
          if(caseExpressions.get(i) instanceof LiteralDefault)
            acceptor.accept(
              severity(shouldBeLast), "A 'default' should appear last", caseExpressions.get(i),
              IPPDiagnostics.ISSUE__DEFAULT_NOT_LAST);
      }
    }

    // Check duplicate by equivalence (mark as duplicate)
    // Check equality to switch expression (mark all others as unreachable),
    for(int i = 0; i < caseExpressions.size(); i++) {
      Expression e1 = caseExpressions.get(i);

      // if a case value is equivalent to the switch expression, all other are unreachable
      if(eqCalculator.isEquivalent(e1, switchExpr))
        for(int u = 0; u < caseExpressions.size(); u++)
          if(u != i)
            unreachables.add(u);

      // or if equal to the case expression e1, that this particular expression is a duplicate (mark both).
      for(int j = i + 1; j < caseExpressions.size(); j++)
        if(eqCalculator.isEquivalent(e1, caseExpressions.get(j)))
          duplicates.addAll(Lists.newArrayList(i, j));
    }

    // mark all that are unreachable
    for(Integer i : unreachables)
      acceptor.acceptWarning("Unreachable case", caseExpressions.get(i), IPPDiagnostics.ISSUE__UNREACHABLE);

    // mark all that are duplicates
    for(Integer i : duplicates)
      acceptor.acceptError("Duplicate case", caseExpressions.get(i), IPPDiagnostics.ISSUE__DUPLICATE_CASE);

  }

  @Check
  public void checkCollectExpression(CollectExpression o) {

    // -- the class reference must have valid class ref format
    final Expression classRefExpr = o.getClassReference();
    if(classRefExpr instanceof LiteralNameOrReference) {
      final String classRefString = ((LiteralNameOrReference) classRefExpr).getValue();
      if(!isCLASSREF(classRefString))
        acceptor.acceptError(
          "Not a well formed class reference.", o, PPPackage.Literals.COLLECT_EXPRESSION__CLASS_REFERENCE,
          INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_CLASSREF);
    }
    else {
      acceptor.acceptError(
        "Not a class reference.", o, PPPackage.Literals.COLLECT_EXPRESSION__CLASS_REFERENCE,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_CLASSREF);
    }

    // -- the rhs expressions do not allow a full set of expressions, an extensive search must be made
    // Note: queries must implement both IQuery and be UnaryExpressions
    UnaryExpression q = (UnaryExpression) o.getQuery();
    Expression queryExpr = q.getExpr();

    // null expression is accepted, if stated it must comply with the simplified expressions allowed
    // for a collect expression
    if(queryExpr != null) {
      CollectChecker cc = new CollectChecker();
      cc.doCheck(queryExpr);
    }
  }

  @Check
  public void checkDefinition(Definition o) {
    internalCheckTopLevelExpressions(o.getStatements());

    String typeLabel = o instanceof HostClassDefinition
        ? "class"
        : "define";
    // Can only be contained by manifest (global scope), or another class.
    EObject container = o.eContainer();
    if(!(container instanceof PuppetManifest || container instanceof HostClassDefinition))
      acceptor.acceptError(
        "A '" + typeLabel + "' may only appear at top level or directly inside a class.", o.eContainer(),
        o.eContainingFeature(), INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_AT_TOPLEVEL_OR_CLASS);

    if(!isCLASSNAME(o.getClassName())) {
      // invalid name
      acceptor.acceptError(
        "Must be a valid name (each segment must start with lower case letter)", o,
        PPPackage.Literals.DEFINITION__CLASS_NAME, IPPDiagnostics.ISSUE__NOT_CLASSNAME);
    }
    else {
      // hyphen in name =
      ValidationPreference hyphens = advisor().hyphensInNames();
      if(hyphens.isWarningOrError()) {
        int hyphenIdx = o.getClassName().indexOf("-");
        if(hyphenIdx >= 0) {
          String message = "Hyphen '-' in name only unofficially supported in some puppet versions.";
          if(hyphens == ValidationPreference.WARNING)
            acceptor.acceptWarning(
              message, o, PPPackage.Literals.DEFINITION__CLASS_NAME, IPPDiagnostics.ISSUE__HYPHEN_IN_NAME);
          else
            acceptor.acceptError(
              message, o, PPPackage.Literals.DEFINITION__CLASS_NAME, IPPDiagnostics.ISSUE__HYPHEN_IN_NAME);
        }
      }
      // reserved ?
      if(PPReservedNameHelper.isReservedClassName(o.getClassName())) {
        acceptor.acceptError(
          "Reserved name", o, PPPackage.Literals.DEFINITION__CLASS_NAME, IPPDiagnostics.ISSUE__RESERVED_NAME);
      }
    }
  }

  @Check
  public void checkDefinitionArgument(DefinitionArgument o) {
    // -- LHS should be a variable, use of name is deprecated
    String argName = o.getArgName();
    ValidationPreference missingDollar;
    if(argName == null || argName.length() < 1)
      acceptor.acceptError(
        "Empty or null argument", o, PPPackage.Literals.DEFINITION_ARGUMENT__ARG_NAME, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__NOT_VARNAME);

    else if(!argName.startsWith("$")) {
      missingDollar = advisor().definitionParamterMissingDollar();
      warningOrError(
        acceptor, missingDollar, (missingDollar.isWarning()
            ? "Deprecation: "
            : "") + "Definition argument should start with $", o,
        PPPackage.Literals.DEFINITION_ARGUMENT__ARG_NAME, IPPDiagnostics.ISSUE__NOT_VARNAME);
    }
    else if(!isVARIABLE(argName))
      acceptor.acceptError(
        "Not a valid variable name", o, PPPackage.Literals.DEFINITION_ARGUMENT__ARG_NAME, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__NOT_VARNAME);

    else if(!isFirstNonDollarLowerCase(argName))
      acceptor.acceptError(
        "A parameter must start with a lower case letter", o, PPPackage.Literals.DEFINITION_ARGUMENT__ARG_NAME,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_INITIAL_LOWERCASE);

    if(o.getOp() != null && !"=".equals(o.getOp()))
      acceptor.acceptError(
        "Must be an assignment operator '=' (not definition '=>')", o,
        PPPackage.Literals.DEFINITION_ARGUMENT__OP, IPPDiagnostics.ISSUE__NOT_ASSIGNMENT_OP);
    // -- RHS should be a rvalue
    internalCheckRvalueExpression(o.getValue());
  }

  @Check
  public void checkDefinitionArgumentList(DefinitionArgumentList o) {
    Set<String> seen = Sets.newHashSet();
    int lastWithoutDefault = -1;
    int counter = 0;
    EList<DefinitionArgument> arguments = o.getArguments();
    for(DefinitionArgument arg : arguments) {
      String s = arg.getArgName();
      if(s.startsWith("$"))
        s = s.substring(1);
      if(seen.contains(s)) {
        acceptor.acceptError(
          "Duplicate definition of parameter", arg, IPPDiagnostics.ISSUE__DUPLICATE_PARAMETER);
      }
      else
        seen.add(s);
      if(arg.getValue() == null)
        lastWithoutDefault = counter;
      counter++;
    }
    if(o.eContainer() instanceof Lambda) {
      int limit = arguments.size();
      for(int i = 0; i < limit; i++) {
        DefinitionArgument arg = arguments.get(i);
        if(arg.getValue() == null)
          continue;
        if(i < lastWithoutDefault)
          acceptor.acceptError(
            "A Lambda parameter with default can not appear before a parameter without default", arg,
            IPPDiagnostics.ISSUE__PARAM_DEFAULT_NOT_LAST);
      }
    }
    IValidationAdvisor advisor = advisor();
    ValidationPreference endComma = advisor.definitionArgumentListEndComma();
    if(endComma.isWarningOrError()) {
      // Check if list ends with optional comma.
      INode n = NodeModelUtils.getNode(o);
      for(INode i : n.getAsTreeIterable().reverse()) {
        if(",".equals(i.getText())) {
          EObject grammarE = i.getGrammarElement();
          if(grammarE instanceof RuleCall && "endComma".equals(((RuleCall) grammarE).getRule().getName())) {
            warningOrError(
              acceptor, endComma, "End comma not allowed in versions < 2.7.8", o, i.getOffset(),
              i.getLength(), IPPDiagnostics.ISSUE__ENDCOMMA);
            return;
          }
          return;
        }
      }
    }
  }

  @Check
  public void checkDoubleQuotedString(DoubleQuotedString o) {
    // Check if a verbatim part starting with '-' follows a VariableTE.
    // If so, issue configurable issue for the VariableTE
    //
    TextExpression previous = null;
    int idx = 0;
    IValidationAdvisor advisor = advisor();
    ValidationPreference hyphens = advisor.interpolatedNonBraceEnclosedHyphens();
    for(TextExpression te : o.getStringPart()) {
      if(idx > 0 && previous instanceof VariableTE && te instanceof VerbatimTE) {
        VerbatimTE verbatim = (VerbatimTE) te;
        if(verbatim.getText().startsWith("-")) {
          if(hyphens.isWarningOrError()) {
            warningOrError(
              acceptor, hyphens,
              "Interpolation continues past '-' in some puppet 2.7 versions", //
              o, PPPackage.Literals.DOUBLE_QUOTED_STRING__STRING_PART, idx - 1,
              IPPDiagnostics.ISSUE__INTERPOLATED_HYPHEN);
          }
        }
      }
      previous = te;
      idx++;
    }
    ValidationPreference booleansInStringForm = advisor.booleansInStringForm();

    BOOLEAN_STRING: if(booleansInStringForm.isWarningOrError()) {
      // Check if string contains "true" or "false"
      String constant = stringConstantEvaluator.doToString(o);
      if(constant == null)
        break BOOLEAN_STRING;
      constant = constant.trim();
      if("true".equals(constant) || "false".equals(constant))
        warningOrError(
          acceptor, booleansInStringForm, "This is not a boolean", o, IPPDiagnostics.ISSUE__STRING_BOOLEAN,
          constant);
    }

    // DQ_STRING_NOT_REQUIRED
    ValidationPreference dqStringNotRequired = advisor.dqStringNotRequired();
    if(dqStringNotRequired.isWarningOrError() && !hasInterpolation(o)) {
      // contains escape sequences?
      String constant = stringConstantEvaluator.doToString(o);
      if(!constant.contains("\\"))
        warningOrError(
          acceptor, dqStringNotRequired, "Double quoted string not required", o,
          IPPDiagnostics.ISSUE__DQ_STRING_NOT_REQUIRED);
    }
    // UNBRACED INTERPOLATION
    ValidationPreference unbracedInterpolation = advisor.unbracedInterpolation();
    if(unbracedInterpolation.isWarningOrError()) {
      for(TextExpression te : o.getStringPart()) {
        if(te.eClass().getClassifierID() == PPPackage.VARIABLE_TE) {
          warningOrError(
            acceptor, unbracedInterpolation, "Unbraced interpolation of variable", te,
            IPPDiagnostics.ISSUE__UNBRACED_INTERPOLATION);
        }
      }
    }
    // SINGLE INTERPOLATION
    ValidationPreference dqStringNotRequiredVariable = advisor.dqStringNotRequiredVariable();
    SINGLE_INTERPOLATION: if(dqStringNotRequiredVariable.isWarningOrError()) {
      if(o.getStringPart().size() == 1) {
        String replacement = null;
        TextExpression te = o.getStringPart().get(0);
        switch(te.eClass().getClassifierID()) {
          case PPPackage.VARIABLE_TE:
            replacement = ((VariableTE) te).getVarName();
            break;
          case PPPackage.EXPRESSION_TE:
            Expression expr = ((ExpressionTE) te).getExpression();
            expr = ((ParenthesisedExpression) expr).getExpr();
            if(expr instanceof LiteralNameOrReference)
              replacement = "$" + ((LiteralNameOrReference) expr).getValue();
            break;
          default:
            break SINGLE_INTERPOLATION;
        }
        if(replacement != null) {
          warningOrError(
            acceptor, dqStringNotRequiredVariable,
            "String contains single interpolated variable. Double quotes not required.", o,
            IPPDiagnostics.ISSUE__DQ_STRING_NOT_REQUIRED_VAR, replacement);
        }
      }
    }
  }

  @Check
  public void checkElseExpression(ElseExpression o) {
    internalCheckTopLevelExpressions(o.getStatements());
    EObject container = o.eContainer();
    if(container instanceof IfExpression || container instanceof ElseExpression ||
        container instanceof ElseIfExpression)
      return;
    if(container instanceof UnlessExpression) {
      if(advisor().allowUnlessElse())
        return;
      acceptor.acceptError(
        "'else' expression can only be used in an 'unless' in puppet version >= 3.2 --parser future", o,
        // o.eContainer(), o.eContainingFeature(), INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    }
    acceptor.acceptError(
      "'else' expression can only be used in an 'if', 'else' or 'elsif'", o.eContainer(), o.eContainingFeature(),
      INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
  }

  @Check
  public void checkElseIfExpression(ElseIfExpression o) {
    internalCheckTopLevelExpressions(o.getThenStatements());
    EObject container = o.eContainer();
    if(container instanceof IfExpression || container instanceof ElseExpression ||
        container instanceof ElseIfExpression)
      return;
    acceptor.acceptError(
      "'elsif' expression can only be used in an 'if', 'else' or 'elsif'", o, o.eContainingFeature(),
      INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
  }

  @Check
  public void checkEqualityExpression(EqualityExpression o) {
    checkOperator(o, "==", "!=");
  }

  @Check
  public void checkFunctionCall(FunctionCall o) {
    if(!(o.getLeftExpr() instanceof LiteralNameOrReference))
      acceptor.acceptError(
        "Must be a name or reference.", o, PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR,
        IPPDiagnostics.ISSUE__NOT_NAME_OR_REF);
    // rest of validation - valid function - is done during linking
  }

  @Check
  public void checkHostClassDefinition(HostClassDefinition o) {
    // Checks performed by checkDefinition, and in PPResourceLinker
  }

  @Check
  public void checkIfExpression(IfExpression o) {
    internalCheckTopLevelExpressions(o.getThenStatements());
    Expression elseStatement = o.getElseStatement();
    if(elseStatement == null || elseStatement instanceof ElseExpression || elseStatement instanceof IfExpression ||
        elseStatement instanceof ElseIfExpression)
      return;
    acceptor.acceptError(
      "If Expression's else part can only be an 'if' or 'elsif'", o,
      PPPackage.Literals.IF_EXPRESSION__ELSE_STATEMENT, INSIGNIFICANT_INDEX,
      IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
  }

  @Check
  public void checkImportExpression(ImportExpression o) {
    if(o.getValues().size() <= 0)
      acceptor.acceptError(
        "Empty import - should be followed by at least one string.", o,
        PPPackage.Literals.IMPORT_EXPRESSION__VALUES, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__REQUIRED_EXPRESSION);
    // warn against interpolation in double quoted strings
    for(IQuotedString s : o.getValues()) {
      if(s instanceof DoubleQuotedString)
        if(hasInterpolation(s))
          acceptor.acceptWarning(
            "String has interpolation expressions that will not be evaluated", s,
            PPPackage.Literals.IMPORT_EXPRESSION__VALUES, o.getValues().indexOf(s),
            IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    }
  }

  @Check
  public void checkInExpression(InExpression o) {
    checkOperator(o, "in");
  }

  @Check
  public void checkLambda(Lambda o) {
    if(!advisor().allowLambdas()) {
      acceptor.acceptError(
        "A Lambda expressions is only available in Puppet version >= 3.2 --parser future. (Change target preference?)",
        o, IPPDiagnostics.ISSUE__UNSUPPORTED_LAMBDA);
    }
    else {
      internalCheckTopLevelExpressions(o.getStatements());
    }
  }

  @Check
  public void checkLiteralName(LiteralName o) {
    if(!isNAME(o.getValue()))
      acceptor.acceptError(
        "Expected to comply with NAME rule", o, PPPackage.Literals.LITERAL_NAME__VALUE, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__NOT_NAME);
  }

  @Check
  public void checkLiteralNameOrReference(LiteralNameOrReference o) {
    if(isKEYWORD(o.getValue())) {
      acceptor.acceptError(
        "Reserved word.", o, PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__RESERVED_WORD);
      return;
    }

    if(isCLASSNAME_OR_REFERENCE(o.getValue()))
      return;
    acceptor.acceptError(
      "Must be a name or type (all segments must start with same case).", o,
      PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE, INSIGNIFICANT_INDEX,
      IPPDiagnostics.ISSUE__NOT_NAME_OR_REF, o.getValue());

  }

  @Check
  public void checkLiteralRegex(LiteralRegex o) {
    if(!isREGEX(o.getValue())) {
      acceptor.acceptError(
        "Expected to comply with Puppet regular expression", o, PPPackage.Literals.LITERAL_REGEX__VALUE,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_REGEX);
      return;
    }
    if(!o.getValue().endsWith("/"))
      acceptor.acceptError(
        "Puppet regular expression does not support flags after end slash", o,
        PPPackage.Literals.LITERAL_REGEX__VALUE, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__UNSUPPORTED_REGEX_FLAGS);
  }

  @Check
  public void checkMatchingExpression(MatchingExpression o) {
    Expression regex = o.getRightExpr();
    if(regex == null || !(regex instanceof LiteralRegex))
      acceptor.acceptError(
        "Right expression must be a regular expression.", o, PPPackage.Literals.BINARY_EXPRESSION__RIGHT_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);

    checkOperator(o, "=~", "!~");
  }

  @Check
  public void checkMethodCall(MethodCall o) {
    if(!advisor().allowLambdas()) { // Note, same check as for lambdas...
      acceptor.acceptError(
        "A call on the form 'a.b()' is only available in Puppet version >= 3.2 --parser future. (Change target preference?)",
        o, IPPDiagnostics.ISSUE__UNSUPPORTED_METHOD_CALL);
    }
    else {
      Expression methodExpr = o.getMethodExpr();
      if(methodExpr == null)
        acceptor.acceptError("Missing function name after '.'", o, IPPDiagnostics.ISSUE__MISSING_METHOD_NAME);
      if(methodExpr instanceof LiteralName == false)
        acceptor.acceptError("Illegal function name", methodExpr, IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    }
  }

  @Check
  public void checkMultiplicativeExpression(MultiplicativeExpression o) {
    if(advisor().allowModulo())
      checkOperator(o, "*", "/", "%");
    else
      checkOperator(o, "*", "/");
    checkNumericBinaryExpression(o);
  }

  @Check
  public void checkNodeDefinition(NodeDefinition o) {
    internalCheckTopLevelExpressions(o.getStatements());

    // Can only be contained by manifest (global scope), or another class.
    EObject container = o.eContainer();
    if(!(container instanceof PuppetManifest || container instanceof HostClassDefinition))
      acceptor.acceptError(
        "A node definition may only appear at toplevel or directly inside classes.", container,
        o.eContainingFeature(), INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_AT_TOPLEVEL_OR_CLASS);

    Expression parentExpr = o.getParentName();
    if(parentExpr != null) {
      String parentName = stringConstantEvaluator.doToString(parentExpr);
      if(parentName == null)
        acceptor.acceptError(
          "Must be a constant name/string expression.", o, PPPackage.Literals.NODE_DEFINITION__PARENT_NAME,
          INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_CONSTANT);
    }
  }

  private void checkNumericBinaryExpression(BinaryOpExpression o) {
    switch(typeEvaluator.numericType(o.getLeftExpr())) {
      case YES:
        break;
      case NO:
        acceptor.acceptError(
          "Operator " + o.getOpName() + " requires numeric value.", o,
          PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR, IPPDiagnostics.ISSUE__NOT_NUMERIC);
        break;
      case INCONCLUSIVE:
    }
    switch(typeEvaluator.numericType(o.getRightExpr())) {
      case YES:
        break;
      case NO:
        acceptor.acceptError(
          "Operator " + o.getOpName() + " requires numeric value.", o,
          PPPackage.Literals.BINARY_EXPRESSION__RIGHT_EXPR, IPPDiagnostics.ISSUE__NOT_NUMERIC);
        break;
      case INCONCLUSIVE:
    }

  }

  protected void checkOperator(BinaryOpExpression o, String... ops) {
    String op = o.getOpName();
    for(String s : ops)
      if(s.equals(op))
        return;
    acceptor.acceptError(
      "Illegal operator: " + (op == null
          ? "null"
          : op), o, PPPackage.Literals.BINARY_OP_EXPRESSION__OP_NAME, INSIGNIFICANT_INDEX,
      IPPDiagnostics.ISSUE__ILLEGAL_OP);

  }

  @Check
  public void checkParenthesisedExpression(ParenthesisedExpression o) {
    if(o.getExpr() == null) {
      final String msg = "Empty expression";
      final String issue = IPPDiagnostics.ISSUE__REQUIRED_EXPRESSION;
      final ICompositeNode node = NodeModelUtils.getNode(o);
      if(node == null)
        acceptor.acceptError(
          msg, o, PPPackage.Literals.PARENTHESISED_EXPRESSION__EXPR, INSIGNIFICANT_INDEX, issue);
      else {
        // if node's text is empty, mark the nodes before/after, if present.
        int textSize = node.getLength();
        INode endNode = textSize == 0 && node.hasNextSibling()
            ? node.getNextSibling()
            : node;
        INode startNode = textSize == 0 && node.hasPreviousSibling()
            ? node.getPreviousSibling()
            : node;

        ((ValidationBasedMessageAcceptor) acceptor).acceptError(
          msg, o, startNode.getOffset(), startNode.getLength() + ((startNode != endNode)
              ? endNode.getLength()
              : 0), issue);
      }
    }
  }

  @Check
  public void checkPuppetManifest(PuppetManifest o) {
    internalCheckTopLevelExpressions(o.getStatements());
    internalCheckComments(o);
  }

  @Check
  public void checkRelationalExpression(RelationalExpression o) {
    String op = o.getOpName();
    if("<".equals(op) || "<=".equals(op) || ">".equals(op) || ">=".equals(op))
      return;
    acceptor.acceptError(
      "Illegal operator.", o, PPPackage.Literals.BINARY_OP_EXPRESSION__OP_NAME, INSIGNIFICANT_INDEX,
      IPPDiagnostics.ISSUE__ILLEGAL_OP);
  }

  /**
   * Checks that a RelationshipExpression
   * only has left and right of type
   * - ResourceExpression (but not a ResourceOverride)
   * - ResourceReference
   * - CollectExpression
   *
   * That the operator name is a valid name (if defined from code).
   * INEDGE : MINUS GT; // '->'
   * OUTEDGE : LT MINUS; // '<-'
   * INEDGE_SUB : TILDE GT; // '~>'
   * OUTEDGE_SUB : LT TILDE; // '<~'
   */
  @Check
  public void checkRelationshipExpression(RelationshipExpression o) {
    // -- Check operator validity
    String opName = o.getOpName();
    if(opName == null ||
        !("->".equals(opName) || "<-".equals(opName) || "~>".equals(opName) || "<~".equals(opName)))
      acceptor.acceptError(
        "Illegal operator: " + opName == null
            ? "null"
            : opName, o, PPPackage.Literals.BINARY_OP_EXPRESSION__OP_NAME, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__ILLEGAL_OP);

    boolean okL = internalCheckRelationshipOperand(
      o, o.getLeftExpr(), PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR);
    boolean okR = internalCheckRelationshipOperand(
      o, o.getRightExpr(), PPPackage.Literals.BINARY_EXPRESSION__RIGHT_EXPR);

    // optionally flag RtoL relationships
    if(opName.startsWith("<")) {
      // what does the advice say
      IValidationAdvisor advisor = advisor();
      ValidationPreference rightToLeft = advisor.rightToLeftRelationships();
      if(rightToLeft.isWarningOrError()) {
        List<INode> x = NodeModelUtils.findNodesForFeature(o, PPPackage.Literals.BINARY_OP_EXPRESSION__OP_NAME);
        // in case there is embedded whitespace or crazy stuff... (locate the node)
        INode theNode = null;
        for(INode n : x) {
          if(n.getGrammarElement() == grammarAccess.getRelationshipExpressionAccess().getOpNameEdgeOperatorParserRuleCall_1_1_0())
            theNode = n;
        }
        // a node should have been found, but just to be safe, report the error on the entire expression if there was none.
        if(theNode != null)
          warningOrError(
            acceptor, rightToLeft, "Resource dependencies using <- or <~ are discouraged", theNode,
            IPPDiagnostics.ISSUE_RIGHT_TO_LEFT_RELATIONSHIP, opName, Boolean.toString(okL && okR));
        else
          warningOrError(
            acceptor, rightToLeft, "Resource dependencies using <- or <~ are discouraged", o,
            IPPDiagnostics.ISSUE_RIGHT_TO_LEFT_RELATIONSHIP, opName, Boolean.toString(okL && okR));
      }
    }
  }

  @Check
  public void checkResourceBody(ResourceBody o) {
    Expression nameExpr = o.getNameExpr();
    // missing name is checked by container (if it is ok or not)
    if(nameExpr == null)
      return;
    if(!(nameExpr instanceof StringExpression ||
        // TODO: was LiteralString, follow up
        nameExpr instanceof LiteralNameOrReference || nameExpr instanceof LiteralName ||
        nameExpr instanceof VariableExpression || nameExpr instanceof AtExpression ||
        nameExpr instanceof LiteralList || nameExpr instanceof SelectorExpression))
      acceptor.acceptError(
        "Expression unsupported as resource name/title.", o, PPPackage.Literals.RESOURCE_BODY__NAME_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION_STRING_OK);

    // prior to 2.7 a qualified name caused problems
    boolean unquotedNameFlagged = false;
    if(!validationAdvisorProvider.get().allowUnquotedQualifiedResourceNames())
      if(nameExpr instanceof LiteralNameOrReference) {
        if(((LiteralNameOrReference) nameExpr).getValue().contains("::")) {
          acceptor.acceptError(
            "Qualified name must be quoted.", o, PPPackage.Literals.RESOURCE_BODY__NAME_EXPR,
            INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__UNQUOTED_QUALIFIED_NAME);
          unquotedNameFlagged = true;
        }
      }
    if(!unquotedNameFlagged && nameExpr instanceof LiteralNameOrReference) {
      IValidationAdvisor advisor = advisor();
      ValidationPreference unquotedResourceTitles = advisor.unquotedResourceTitles();
      if(unquotedResourceTitles.isWarningOrError()) {
        warningOrError(
          acceptor, unquotedResourceTitles, "Unquoted resource title", nameExpr,
          IPPDiagnostics.ISSUE__UNQUOTED_QUALIFIED_NAME);
      }
    }
    ValidationPreference ensureFirstAdvise = advisor().ensureShouldAppearFirstInResource();
    if(ensureFirstAdvise.isWarningOrError() && o.getAttributes() != null) {
      int ix = 0;
      for(AttributeOperation ao : o.getAttributes().getAttributes()) {
        // is first ensure, if not, find it and mark it
        if("ensure".equals(ao.getKey()) && ix != 0)
          warningOrError(
            acceptor, ensureFirstAdvise, "Resource property 'ensure' not stated first", ao,
            PPPackage.Literals.ATTRIBUTE_OPERATION__KEY, IPPDiagnostics.ISSUE__ENSURE_NOT_FIRST);
        ix++;
      }

    }
  }

  /**
   * Checks ResourceExpression and derived VirtualResourceExpression.
   *
   * @param o
   */
  @Check
  public void checkResourceExpression(ResourceExpression o) {
    classifier.classify(o);
    ClassifierAdapter adapter = ClassifierAdapterFactory.eINSTANCE.adapt(o);
    int resourceType = adapter.getClassifier();

    if(resourceType == RESOURCE_IS_BAD) {
      acceptor.acceptError(
        "Resource type must be a literal name, 'class', class reference, or a resource reference.", o,
        PPPackage.Literals.RESOURCE_EXPRESSION__RESOURCE_EXPR, INSIGNIFICANT_INDEX,
        ISSUE__RESOURCE_BAD_TYPE_FORMAT);
      // not much use checking the rest
      return;
    }

    // -- can not virtualize/export non regular resources
    if(o.getResourceExpr() instanceof VirtualNameOrReference && resourceType != RESOURCE_IS_REGULAR) {
      acceptor.acceptError(
        "Only regular resources can be virtual", o, PPPackage.Literals.RESOURCE_EXPRESSION__RESOURCE_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__RESOURCE_NOT_VIRTUALIZEABLE);
    }
    boolean onlyOneBody = resourceType == RESOURCE_IS_DEFAULT || resourceType == RESOURCE_IS_OVERRIDE;
    boolean titleExpected = !onlyOneBody;
    boolean attrAdditionAllowed = resourceType == RESOURCE_IS_OVERRIDE;

    String errorStartText = "Resource ";
    switch(resourceType) {
      case RESOURCE_IS_OVERRIDE:
        errorStartText = "Resource override ";
        break;
      case RESOURCE_IS_DEFAULT:
        errorStartText = "Resource defaults ";
        break;
      case RESOURCE_IS_CLASSPARAMS:
        errorStartText = "Class parameter defaults ";
        break;
    }

    // check multiple bodies
    if(onlyOneBody && o.getResourceData().size() > 1)
      acceptor.acceptError(
        errorStartText + "can not have multiple resource instances.", o,
        PPPackage.Literals.RESOURCE_EXPRESSION__RESOURCE_DATA, INSIGNIFICANT_INDEX,
        ISSUE__RESOURCE_MULTIPLE_BODIES);

    // rules for body:
    // - should not have a title (deprecated for default, but not allowed
    // for override)
    // TODO: Make deprecation error optional for default
    // - only override may have attribute additions
    //

    // check title

    List<String> titles = Lists.newArrayList();
    for(ResourceBody body : o.getResourceData()) {
      boolean hasTitle = body.getNameExpr() != null; // && body.getName().length() > 0;
      if(titleExpected) {
        if(!hasTitle)
          acceptor.acceptError(
            errorStartText + "must have a title.", body, PPPackage.Literals.RESOURCE_BODY__NAME_EXPR,
            INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__RESOURCE_WITHOUT_TITLE);
        else {
          // TODO: Validate the expression type

          // check for uniqueness (within same resource expression)
          if(body.getNameExpr() instanceof LiteralList) {
            int index = 0;
            for(Expression ne : ((LiteralList) body.getNameExpr()).getElements()) {
              String titleString = stringConstantEvaluator.doToString(ne);
              if(titleString != null) {
                if(titles.contains(titleString)) {
                  acceptor.acceptError(
                    errorStartText + "redefinition of: " + titleString, body.getNameExpr(),
                    PPPackage.Literals.LITERAL_LIST__ELEMENTS, index,
                    IPPDiagnostics.ISSUE__RESOURCE_NAME_REDEFINITION);
                }
                else
                  titles.add(titleString);
              }
              index++;
            }
          }
          else {
            String titleString = stringConstantEvaluator.doToString(body.getNameExpr());
            if(titleString != null) {
              if(titles.contains(titleString)) {
                acceptor.acceptError(
                  errorStartText + "redefinition of: " + titleString, body,
                  PPPackage.Literals.RESOURCE_BODY__NAME_EXPR, INSIGNIFICANT_INDEX,
                  IPPDiagnostics.ISSUE__RESOURCE_NAME_REDEFINITION);
              }
              else
                titles.add(titleString);
            }
          }
        }
      }
      else if(hasTitle) {
        acceptor.acceptError(
          errorStartText + " can not have a title", body, PPPackage.Literals.RESOURCE_BODY__NAME_EXPR,
          INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__RESOURCE_WITH_TITLE);
      }

      // ensure that only resource override has AttributeAdditions
      if(!attrAdditionAllowed && body.getAttributes() != null) {
        for(AttributeOperation ao : body.getAttributes().getAttributes()) {
          if("+>".equals(ao.getOp())) // instanceof AttributeAddition)
            acceptor.acceptError(
              errorStartText + " can not have attribute additions.", body,
              PPPackage.Literals.RESOURCE_BODY__ATTRIBUTES,
              body.getAttributes().getAttributes().indexOf(ao),
              IPPDiagnostics.ISSUE__RESOURCE_WITH_ADDITIONS);
        }
      }
    }

    // --Check Resource Override (the AtExpression)
    if(resourceType == RESOURCE_IS_OVERRIDE) {
      if(isStandardAtExpression((AtExpression) o.getResourceExpr()))
        acceptor.acceptError(
          "Resource override can not be done with array/hash access", o,
          PPPackage.Literals.RESOURCE_EXPRESSION__RESOURCE_EXPR, INSIGNIFICANT_INDEX,
          IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    }
  }

  @Check
  public void checkSelectorEntry(SelectorEntry o) {
    Expression lhs = o.getLeftExpr();
    if(!isSELECTOR_LHS(lhs))
      acceptor.acceptError(
        "Not an acceptable selector entry left hand side expression. Was: " +
            expressionTypeNameProvider.doToString(lhs), o, PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
    // TODO: check rhs is "rvalue"
  }

  @Check
  public void checkSelectorExpression(SelectorExpression o) {
    Expression lhs = o.getLeftExpr();

    // -- non null lhs, and must be an acceptable lhs value for selector
    if(lhs == null)
      acceptor.acceptError(
        "A selector expression must have a left expression", o,
        PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__NULL_EXPRESSION);
    else if(!isSELECTOR_LHS(lhs))
      acceptor.acceptError(
        "Not an acceptable selector left hand side expression", o,
        PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);

    // -- there must be at least one parameter
    if(o.getParameters().size() < 1)
      acceptor.acceptError(
        "A selector expression must have at least one right side entry", o,
        PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__NULL_EXPRESSION);

    // -- all parameters must be SelectorEntry instances
    // -- one of them should have LiteralDefault as left expr
    // -- there should only be one default
    boolean theDefaultIsSeen = false;
    IValidationAdvisor advisor = advisor();
    // collect unreachable entries to avoid multiple unreachable markers for an entry
    Set<Integer> unreachables = Sets.newHashSet();
    Set<Integer> duplicates = Sets.newHashSet();
    List<Expression> caseExpressions = Lists.newArrayList();

    for(Expression e : o.getParameters()) {
      if(!(e instanceof SelectorEntry)) {
        acceptor.acceptError(
          "Must be a selector entry. Was:" + expressionTypeNameProvider.doToString(e), o,
          PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, o.getParameters().indexOf(e),
          IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
        caseExpressions.add(null); // to be skipped later
      }
      else {
        // it is a selector entry
        SelectorEntry se = (SelectorEntry) e;
        Expression e1 = se.getLeftExpr();
        caseExpressions.add(e1);
        if(e1 instanceof LiteralDefault)
          theDefaultIsSeen = true;
      }
    }

    ValidationPreference defaultLast = advisor.selectorDefaultShouldAppearLast();
    if(defaultLast.isWarningOrError() && theDefaultIsSeen) {
      for(int i = 0; i < caseExpressions.size() - 1; i++) {
        Expression e1 = caseExpressions.get(i);
        if(e1 == null)
          continue;
        if(e1 instanceof LiteralDefault) {
          acceptor.accept(
            severity(defaultLast), "A 'default' should be placed last", e1,
            IPPDiagnostics.ISSUE__DEFAULT_NOT_LAST);
        }
      }
    }

    // check that there is a default
    if(!theDefaultIsSeen) {
      ValidationPreference missingDefaultInSelector = advisor.missingDefaultInSelector();
      if(missingDefaultInSelector.isWarningOrError())
        acceptor.accept(
          severity(missingDefaultInSelector), "Missing 'default' selector case", o,
          IPPDiagnostics.ISSUE__MISSING_DEFAULT);
    }

    // Check unreachable by equivalence
    // If a case expr is the same as the switch, all other are unreachable
    // Check for duplicates
    for(int i = 0; i < caseExpressions.size(); i++) {
      Expression e1 = caseExpressions.get(i);
      if(e1 == null)
        continue;
      if(eqCalculator.isEquivalent(e1, o.getLeftExpr()))
        for(int u = 0; u < caseExpressions.size(); u++) {
          if(i == u || caseExpressions.get(u) == null)
            continue;
          unreachables.add(u);
        }
      for(int j = i + 1; j < caseExpressions.size(); j++) {
        Expression e2 = caseExpressions.get(j);
        if(e2 == null)
          continue;
        if(eqCalculator.isEquivalent(e1, e2)) {
          duplicates.add(i);
          duplicates.add(j);
        }
      }
    }

    for(Integer i : unreachables)
      if(caseExpressions.get(i) != null)
        acceptor.acceptWarning("Unreachable", caseExpressions.get(i), IPPDiagnostics.ISSUE__UNREACHABLE);

    for(Integer i : duplicates)
      if(caseExpressions.get(i) != null)
        acceptor.acceptError(
          "Duplicate selector case", caseExpressions.get(i), IPPDiagnostics.ISSUE__DUPLICATE_CASE);

    // check missing comma between entries
    final int count = o.getParameters().size();
    EList<Expression> params = o.getParameters();
    for(int i = 0; i < count - 1; i++) {
      // do not complain about missing ',' if expression is not a selector entry
      Expression e1 = params.get(i);
      if(e1 instanceof SelectorEntry == false)
        continue;
      INode n = NodeModelUtils.getNode(e1);
      INode n2 = NodeModelUtils.getNode(params.get(i + 1));

      INode commaNode = null;
      for(commaNode = n.getNextSibling(); commaNode != null; commaNode = commaNode.getNextSibling())
        if(commaNode == n2)
          break;
        else if(commaNode instanceof LeafNode && ((LeafNode) commaNode).isHidden())
          continue;
        else
          break;

      if(commaNode == null || !",".equals(commaNode.getText())) {
        int expectOffset = n.getTotalOffset() + n.getTotalLength();
        acceptor.acceptError("Missing comma.", n.getSemanticElement(),
        // note that offset must be -1 as this ofter a hidden newline and this
        // does not work otherwise. Any quickfix needs to adjust the offset on replacement.
          expectOffset - 1, 2, IPPDiagnostics.ISSUE__MISSING_COMMA);
      }
    }

  }

  @Check
  public void checkSeparatorExpression(SeparatorExpression o) {
    if(!advisor().allowSeparatorExpression())
      acceptor.acceptError(
        "The ';' expression separator is only available in Puppet version >= 3.2 --parser future. (Change target preference?)",
        o, IPPDiagnostics.ISSUE__UNSUPPORTED_SEPARATOR);
  }

  @Check
  public void checkShiftExpression(ShiftExpression o) {
    checkOperator(o, "<<", ">>");
    checkNumericBinaryExpression(o);
  }

  @Check
  public void checkSingleQuotedString(SingleQuotedString o) {
    if(!isSTRING(o.getText()))
      acceptor.acceptError(
        "Contains illegal character(s). Probably an unescaped single quote.", o,
        PPPackage.Literals.SINGLE_QUOTED_STRING__TEXT, IPPDiagnostics.ISSUE__NOT_STRING);

    IValidationAdvisor advisor = advisor();
    ValidationPreference booleansInStringForm = advisor.booleansInStringForm();

    if(booleansInStringForm.isWarningOrError()) {
      // Check if string contains "true" or "false"
      String constant = o.getText();
      if(constant != null) {
        constant = constant.trim();
        if("true".equals(constant) || "false".equals(constant))
          acceptor.accept(
            severity(booleansInStringForm), "This is not a boolean", o,
            IPPDiagnostics.ISSUE__STRING_BOOLEAN, constant);
      }
    }

  }

  @Check
  public void checkUnaryExpression(UnaryMinusExpression o) {
    if(o.getExpr() == null)
      acceptor.acceptError(
        "An unary minus expression must have right hand side expression", o,
        PPPackage.Literals.UNARY_EXPRESSION__EXPR, //
        INSIGNIFICANT_INDEX, //
        IPPDiagnostics.ISSUE__NULL_EXPRESSION);
  }

  @Check
  public void checkUnaryExpression(UnaryNotExpression o) {
    if(o.getExpr() == null)
      acceptor.acceptError("A not expression must have a righ hand side expression", o, //
        PPPackage.Literals.UNARY_EXPRESSION__EXPR, INSIGNIFICANT_INDEX, //
        IPPDiagnostics.ISSUE__NULL_EXPRESSION);
  }

  @Check
  void checkUnlessExpression(UnlessExpression o) {
    internalCheckTopLevelExpressions(o.getThenStatements());
    if(!advisor().allowUnless()) {
      acceptor.acceptError(
        "The 'unless' statment is only available in Puppet version >= 3.0. (Change target preference?)", o,
        IPPDiagnostics.ISSUE__UNSUPPORTED_UNLESS);
    }
  }

  @Check
  public void checkUnquotedString(UnquotedString o) {
    // Turns out these are not supported at all !
    acceptor.acceptError("Unquoted interpolation is not supported", o, IPPDiagnostics.ISSUE__UNQUOTED_INTERPOLATION);
  }

  @Check
  public void checkVariableExpression(VariableExpression o) {
    if(!isVARIABLE(o.getVarName()))
      acceptor.acceptError(
        "Expected to comply with Variable rule", o, PPPackage.Literals.VARIABLE_EXPRESSION__VAR_NAME,
        INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_VARNAME);
  }

  @Check
  void checkVariableTextExpression(VariableTE o) {
    // TODO: There is not much that can go wrong here, but should protect against manual model problems (like not a valid variable name.
  }

  @Check
  public void checkVerbatimTextExpression(VerbatimTE o) {
    String s = o.getText();
    if(s == null || s.length() == 0)
      return;
    // remove all escaped \ to make it easier to find the illegal escapes
    Matcher m1 = patternHelper.getRecognizedDQEscapePattern().matcher(s);
    s = m1.replaceAll("");

    Matcher m = patternHelper.getUnrecognizedDQEscapesPattern().matcher(s);
    StringBuffer unrecognized = new StringBuffer();
    while(m.find())
      unrecognized.append(m.group());
    if(unrecognized.length() > 0)
      acceptor.acceptWarning(
        "Unrecognized escape sequence(s): " + unrecognized.toString(), o,
        IPPDiagnostics.ISSUE__UNRECOGNIZED_ESCAPE);
  }

  /**
   * NOTE: Adds validation to the puppet package (in 1.0 the package was not added
   * automatically, in 2.0 it is.
   */
  @Override
  protected List<EPackage> getEPackages() {
    List<EPackage> result = super.getEPackages();
    if(!result.contains(PPPackage.eINSTANCE))
      result.add(PPPackage.eINSTANCE);

    return result;
  }

  protected boolean hasInterpolation(IQuotedString s) {
    if(!(s instanceof DoubleQuotedString))
      return false;
    return TextExpressionHelper.hasInterpolation((DoubleQuotedString) s);
  }

  public void internalCheckComments(PuppetManifest o) {
    ValidationPreference mlComments = validationAdvisorProvider.get().mlComments();
    if(mlComments.isWarningOrError()) {
      INode root = NodeModelUtils.getNode(o);
      if(root != null) {
        root = root.getRootNode();
      }
      for(INode n : root.getAsTreeIterable()) {
        if(n.getGrammarElement() == grammarAccess.getML_COMMENTRule())
          warningOrError(
            acceptor, mlComments, "Comments using /* */ are discouraged", n,
            IPPDiagnostics.ISSUE_UNWANTED_ML_COMMENT, Boolean.toString(n.getText().endsWith("\n")));
      }
    }

  }

  protected void internalCheckEmptyExpression(EList<Expression> statements) {
    if(statements == null || statements.size() == 0)
      return;
    // check that Separator is not first, and that separator expressions are not adjacent
    int limit = statements.size();
    boolean prevSep = false;
    for(int i = 0; i < limit; i++) {
      Expression s = statements.get(i);
      boolean isSep = s instanceof SeparatorExpression;
      if(isSep && (i == 0 || prevSep)) {
        // First is separator, or this is separator and previous was too
        acceptor.acceptError(
          "Empty statement", s.eContainer(), s.eContainingFeature(), i, IPPDiagnostics.ISSUE__EMPTY_STATEMENT);
      }
      prevSep = isSep;
    }
  }

  private boolean internalCheckRelationshipOperand(RelationshipExpression r, Expression o, EReference feature) {
    boolean result = true;
    // if extended is true, allow these puppet grammar elements:
    // (resource | resourceref | collection | variable | quoted text | selector | case statement | hasharrayaccesses)
    final boolean extended = advisor().allowExtendedDependencyTypes();

    // -- chained relationsips A -> B -> C
    if(o instanceof RelationshipExpression)
      return result; // ok, they are chained

    // first check classes where validity depends on semantics
    if(o instanceof ResourceExpression) {
      // may not be a resource override
      ResourceExpression re = (ResourceExpression) o;
      if(re.getResourceExpr() instanceof AtExpression) {
        acceptor.acceptError(
          "Dependency can not be defined for a resource override.", r, feature, INSIGNIFICANT_INDEX,
          IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
        result = false;
      }
    }
    else if(o instanceof AtExpression) {
      // extended allows hasharray access, so any form of AtExpression is legal
      if(!extended) {
        // the AtExpression is validated as standard or resource reference, so only need
        // to check correct form
        if(isStandardAtExpression((AtExpression) o)) {
          acceptor.acceptError(
            "Dependency can not be formed for an array/hash access", r, feature, INSIGNIFICANT_INDEX,
            IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
          result = false;
        }
      }
    }
    else if(o instanceof VirtualNameOrReference) {
      acceptor.acceptError(
        "Dependency can not be formed for virtual resource", r, feature, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
      result = false;
    }
    // then check classes where further semantic checks are not required
    else if(!((extended && extendedRelationshipClasses.contains(o.eClass())) || o instanceof CollectExpression)) {
      acceptor.acceptError(
        "Dependency can not be formed for this type of expression", r, feature, INSIGNIFICANT_INDEX,
        IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION);
      result = false;
    }
    return result;
  }

  protected void internalCheckRvalueExpression(EList<Expression> statements) {
    for(Expression expr : statements)
      internalCheckRvalueExpression(expr);
  }

  protected void internalCheckRvalueExpression(Expression expr) {
    if(expr == null)
      return;
    for(Class<?> c : rvalueClasses) {
      if(c.isAssignableFrom(expr.getClass()))
        return;
    }
    // Also allow constructs that are "epxressions" in puppet grammar
    // TODO: This should probably be configurable per Puppet target version.
    //
    for(Class<?> c : expressionClasses) {
      if(c.isAssignableFrom(expr.getClass()))
        return;
    }

    acceptor.acceptError(
      "Not a right hand side value. Was: " + expressionTypeNameProvider.doToString(expr), expr.eContainer(),
      expr.eContainingFeature(), INSIGNIFICANT_INDEX, IPPDiagnostics.ISSUE__NOT_RVALUE);

  }

  protected void internalCheckTopLevelExpressions(EList<Expression> statements) {
    if(statements == null || statements.size() == 0)
      return;

    internalCheckEmptyExpression(statements);

    boolean allowExprLast = advisor().allowExpressionLastInBlocks();

    // check that all statements are valid as top level statements
    int limit = statements.size();

    // Adjust limit to exclude all trailing separators (e.g. a; a;;;;)
    for(int i = limit - 1; i >= 0; i--)
      if(statements.get(i) instanceof SeparatorExpression)
        limit--;

    each_top: for(int i = 0; i < limit; i++) {
      Expression s = statements.get(i);
      // -- may be a non parenthesized function call
      if(s instanceof LiteralNameOrReference) {
        // There must be one more expression in the list (a single argument, or
        // an Expression list) unless the expression is last and allowExprLast (i.e. a return value)
        if((i + 1) >= limit && !allowExprLast) {
          acceptor.acceptError(
            "Not a top level expression. (Looks like a function call without arguments, use '()')",
            s.eContainer(), s.eContainingFeature(), i, IPPDiagnostics.ISSUE__NOT_TOPLEVEL);
          // continue each_top;
        }
        // the next expression is consumed as a single arg, or an expr list
        i++;
        if(i < limit) {
          // Check expressions that can not be used as arguments
          Expression arg = statements.get(i);
          if(arg instanceof SeparatorExpression)
            acceptor.acceptError(
              "A function call without arguments must use '()')", s.eContainer(), s.eContainingFeature(),
              i - 1, IPPDiagnostics.ISSUE__NOT_TOPLEVEL);
        }
        continue each_top;
      }
      for(Class<?> c : topLevelExprClasses) {
        if(c.isAssignableFrom(s.getClass()))
          continue each_top;
      }
      if(i + 1 == limit && allowExprLast)
        continue each_top;

      acceptor.acceptError(
        "Not a top level expression. Was: " + expressionTypeNameProvider.doToString(s), s.eContainer(),
        s.eContainingFeature(), i, IPPDiagnostics.ISSUE__NOT_TOPLEVEL);
    }

  }

  private boolean isCLASSNAME(String s) {
    return patternHelper.isCLASSNAME(s);
  }

  private boolean isCLASSNAME_OR_REFERENCE(String s) {
    return patternHelper.isCLASSNAME(s) || patternHelper.isCLASSREF(s) || patternHelper.isNAME(s);
  }

  private boolean isCLASSREF(String s) {
    return patternHelper.isCLASSREF(s);
  }

  private boolean isFirstNameInTE(LiteralNameOrReference aName) {
    // If parented by At expression, or Paranethesized Expression
    // and this parent is parented by an ExpressionTE
    // and the total offset of the ExpressionTE + 2 == the offset of the name
    INode nameNode = NodeModelUtils.getNode(aName);
    if(nameNode == null)
      return false; // No node model, should not create LiteralNameOrReference when created from a model, use Variable
    int offset = nameNode.getTotalOffset();
    // find the first parent that is an ExpressionTE
    for(EObject container = aName.eContainer(); container != null; container = container.eContainer()) {
      if(container instanceof ExpressionTE) {
        INode containerNode = NodeModelUtils.getNode(container);
        if((containerNode.getTotalOffset() + 2) == offset)
          return true;
        return false;
      }
    }
    return false;
  }

  private boolean isFirstNonDollarLowerCase(String s) {
    int pos = s.startsWith("$")
        ? 1
        : 0;
    if(s.length() <= pos)
      return false;
    return Character.isLowerCase(s.charAt(pos));
  }

  // NOT considered to be keywords:
  // "class" - it is used when describing defaults TODO: follow up after migration to 2.0 model
  private boolean isKEYWORD(String s) {
    if(s == null || s.length() < 1)
      return false;
    for(String k : keywords)
      if(k.equals(s))
        return true;
    return false;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.xtext.validation.AbstractInjectableValidator#isLanguageSpecific()
   * ISSUE: See https://bugs.eclipse.org/bugs/show_bug.cgi?id=335624
   * TODO: remove work around now that issue is fixed.
   */
  @Override
  public boolean isLanguageSpecific() {
    // return super.isLanguageSpecific(); // when issue is fixed, or remove method
    return false;
  }

  private boolean isNAME(String s) {
    return patternHelper.isNAME(s);
  }

  private boolean isREGEX(String s) {
    return patternHelper.isREGEXP(s);
  }

  /**
   * Checks acceptable expression types for SELECTOR lhs
   *
   * @param lhs
   * @return
   */
  protected boolean isSELECTOR_LHS(Expression lhs) {
    // the lhs can be one of:
    // name, type, quotedtext, variable, funccall, boolean, undef, default, or regex.
    // Or after fix of puppet issue #5515 also hash/At
    if(lhs instanceof StringExpression ||
        // TODO: was LiteralString follow up
        lhs instanceof LiteralName || lhs instanceof LiteralNameOrReference ||
        lhs instanceof VariableExpression || lhs instanceof FunctionCall || lhs instanceof LiteralBoolean ||
        lhs instanceof LiteralUndef || lhs instanceof LiteralRegex || lhs instanceof LiteralDefault)
      return true;
    if(advisor().allowHashInSelector() && lhs instanceof AtExpression)
      return true;
    return false;
  }

  private boolean isStandardAtExpression(AtExpression o) {
    // an At expression is standard if the lhs is a variable or an AtExpression
    Expression lhs = o.getLeftExpr();
    return (lhs instanceof VariableExpression || lhs instanceof AtExpression || (lhs instanceof LiteralNameOrReference && isFirstNameInTE((LiteralNameOrReference) lhs)));

  }

  private boolean isSTRING(String s) {
    return patternHelper.isSQSTRING(s);
  }

  private boolean isVARIABLE(String s) {
    return patternHelper.isVARIABLE(s);
  }

  private Severity severity(ValidationPreference pref) {
    return pref.isError()
        ? Severity.ERROR
        : Severity.WARNING;
  }

  private void warningOrError(IMessageAcceptor acceptor, ValidationPreference validationPreference, String message,
      EObject o, EAttribute feature, String issue) {
    if(validationPreference.isWarning())
      acceptor.acceptWarning(message, o, feature, issue);
    else if(validationPreference.isError())
      acceptor.acceptError(message, o, feature, issue);
  }

  private void warningOrError(IMessageAcceptor acceptor, ValidationPreference validationPreference, String message,
      EObject o, EStructuralFeature feature, int index, String issueCode) {
    if(validationPreference.isWarning())
      acceptor.acceptWarning(message, o, feature, index, issueCode);
    else if(validationPreference.isError())
      acceptor.acceptWarning(message, o, feature, index, issueCode);
  }

  private void warningOrError(IMessageAcceptor acceptor, ValidationPreference validationPreference, String message,
      EObject o, int offset, int length, String issue, String... data) {
    if(validationPreference.isWarning())
      acceptor.acceptWarning(message, o, offset, length, issue, data);
    else if(validationPreference.isError())
      acceptor.acceptError(message, o, offset, length, issue, data);
  }

  private void warningOrError(IMessageAcceptor acceptor, ValidationPreference validationPreference, String message,
      EObject o, String issueCode, String... data) {
    if(validationPreference.isWarning())
      acceptor.acceptWarning(message, o, issueCode, data);
    else if(validationPreference.isError())
      acceptor.acceptError(message, o, issueCode, data);
  }

  private void warningOrError(IMessageAcceptor acceptor, ValidationPreference validationPreference, String message,
      INode n, String issueCode, String... data) {
    warningOrError(
      acceptor, validationPreference, message, n.getSemanticElement(), n.getOffset(), n.getLength(), issueCode,
      data);
  }
}
TOP

Related Classes of com.puppetlabs.geppetto.pp.dsl.validation.PPJavaValidator$CollectChecker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.