Package com.puppetlabs.geppetto.pp.dsl.ui.quickfix

Source Code of com.puppetlabs.geppetto.pp.dsl.ui.quickfix.PPQuickfixProvider

/**
* 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.ui.quickfix;

import java.util.List;

import com.puppetlabs.geppetto.pp.AttributeOperation;
import com.puppetlabs.geppetto.pp.AttributeOperations;
import com.puppetlabs.geppetto.pp.DoubleQuotedString;
import com.puppetlabs.geppetto.pp.LiteralNameOrReference;
import com.puppetlabs.geppetto.pp.PPPackage;
import com.puppetlabs.geppetto.pp.PuppetManifest;
import com.puppetlabs.geppetto.pp.VariableExpression;
import com.puppetlabs.geppetto.pp.VariableTE;
import com.puppetlabs.geppetto.pp.dsl.contentassist.PPProposalsGenerator;
import com.puppetlabs.geppetto.pp.dsl.linking.PPFinder;
import com.puppetlabs.geppetto.pp.dsl.linking.PPSearchPath.ISearchPathProvider;
import com.puppetlabs.geppetto.pp.dsl.ui.labeling.PPDescriptionLabelProvider;
import com.puppetlabs.geppetto.pp.dsl.validation.IPPDiagnostics;
import com.puppetlabs.xtext.dommodel.formatter.comments.CommentProcessor;
import com.puppetlabs.xtext.dommodel.formatter.comments.CommentProcessor.CommentFormattingOptions;
import com.puppetlabs.xtext.dommodel.formatter.comments.CommentProcessor.CommentText;
import com.puppetlabs.xtext.dommodel.formatter.comments.ICommentConfiguration;
import com.puppetlabs.xtext.dommodel.formatter.comments.ICommentConfiguration.CommentType;
import com.puppetlabs.xtext.dommodel.formatter.comments.ICommentContainerInformation;
import com.puppetlabs.xtext.dommodel.formatter.comments.ICommentContainerInformation.HashSLCommentContainer;
import com.puppetlabs.xtext.dommodel.formatter.comments.ICommentContainerInformation.JavaLikeMLCommentContainer;
import com.puppetlabs.xtext.dommodel.formatter.context.IFormattingContextFactory;
import com.puppetlabs.xtext.dommodel.formatter.context.IFormattingContextFactory.FormattingOption;
import com.puppetlabs.xtext.resource.ResourceAccessScope;
import com.puppetlabs.xtext.textflow.CharSequences;
import com.puppetlabs.xtext.textflow.TextFlow;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.edit.IModification;
import org.eclipse.xtext.ui.editor.model.edit.IModificationContext;
import org.eclipse.xtext.ui.editor.model.edit.ISemanticModification;
import org.eclipse.xtext.ui.editor.quickfix.DefaultQuickfixProvider;
import org.eclipse.xtext.ui.editor.quickfix.Fix;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionAcceptor;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.eclipse.xtext.validation.Issue;

import com.google.inject.Inject;
import com.google.inject.Provider;

public class PPQuickfixProvider extends DefaultQuickfixProvider {
  @Inject
  protected PPDescriptionLabelProvider descriptionLabelProvider;

  private final static EClass[] PARAMS_AND_VARIABLES = { //
  //
      PPPackage.Literals.DEFINITION_ARGUMENT, //
      // PPTPPackage.Literals.TYPE_ARGUMENT, //
      PPPackage.Literals.VARIABLE_EXPRESSION };

  private static String toInitialCase(String s, boolean upper) {
    if(s.length() < 1)
      return s;
    StringBuilder builder = new StringBuilder();
    if(upper)
      builder.append(Character.toUpperCase(s.charAt(0)));
    else
      builder.append(Character.toLowerCase(s.charAt(0)));
    try {
      builder.append(s.substring(1));
    }
    catch(IndexOutOfBoundsException e) {
      // ignore
    }
    return builder.toString();
  }

  /**
   * Access to naming of model elements.
   */
  @Inject
  IQualifiedNameProvider fqnProvider;

  /**
   * PP FQN to/from Xtext QualifiedName converter.
   */
  @Inject
  IQualifiedNameConverter converter;

  @Inject
  private PPProposalsGenerator proposer;

  @Inject
  private ISearchPathProvider searchPathProvider;

  @Inject
  private PPFinder ppFinder;

  @Inject
  IFormattingContextFactory formattingContextFactory;

  @Inject
  protected Provider<ICommentConfiguration<CommentType>> commentConfigurationProvider;

  @Inject
  private ResourceAccessScope resourceScope;

  @Inject
  private Provider<RelationshipExpressionFixer> relationshipExpressionFixer;

  @Fix(IPPDiagnostics.ISSUE_UNWANTED_ML_COMMENT)
  public void changeMLCommentToSLComment(final Issue issue, final IssueResolutionAcceptor acceptor) {
    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);
    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {
        resourceScope.enter(state);
        try {

          String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());
          final boolean endsWithBreak = issue.getData() != null && issue.getData().length == 1 &&
              "true".equals(issue.getData()[0]);
          CommentProcessor commentProcessor = new CommentProcessor();
          JavaLikeMLCommentContainer mlContainer = new ICommentContainerInformation.JavaLikeMLCommentContainer();
          HashSLCommentContainer hashContainer = new ICommentContainerInformation.HashSLCommentContainer();
          int offsetOfNode = issue.getOffset();

          int posOnLine = offsetOfNode -
              Math.max(0, 1 + CharSequences.lastIndexOf(
                xtextDocument.get(0, xtextDocument.getLength()), "\n", offsetOfNode - 1));

          CommentText commentText = commentProcessor.separateCommentFromContainer(
            issueString, mlContainer.create(posOnLine), "\n");
          TextFlow result = commentProcessor.formatComment(
            commentText, hashContainer.create(posOnLine), new CommentFormattingOptions(
              commentConfigurationProvider.get().getFormatterAdvice(CommentType.SingleLine),
              Integer.MAX_VALUE, 0, 1), formattingContextFactory.create(state, FormattingOption.Format));

          if(!endsWithBreak)
            result.appendBreak();
          String replacement = CharSequences.trimLeft(result.getText()).toString();

          acceptor.accept(
            issue, "Change to # style comment",
            "Changes comment to # style (any trailing logic on last line is moved to separate line", null,
            new ReplacingModification(issue.getOffset(), issue.getLength(), replacement));
        }
        finally {
          resourceScope.exit();
        }
      }
    });
  }

  @Fix(IPPDiagnostics.ISSUE__UNBRACED_INTERPOLATION)
  public void changeToBracedInterpolation(final Issue issue, final IssueResolutionAcceptor acceptor) {
    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);
    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {

        String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());

        acceptor.accept(issue, "Surround interpolated variable with ${ }", //
          "Changes '" + issueString + "' to '${" + issueString.substring(1) + "}'", null, //
          new SurroundWithTextModification(issue.getOffset() + 1, issueString.length() - 1, "{", "}"));
      }
    });
  }

  @Fix(IPPDiagnostics.ISSUE__NOT_INITIAL_LOWERCASE)
  public void changeToInitialLowerCase(final Issue issue, final IssueResolutionAcceptor acceptor) {
    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);
    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {
        String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());
        int pos = issueString.startsWith("$")
            ? 1
            : 0;
        if(issueString.length() > pos) {
          char c = issueString.charAt(pos);
          if(Character.isLetter(c)) {
            StringBuilder builder = new StringBuilder();
            builder.append("Change '").append(c).append("' to '").append(Character.toLowerCase(c)).append(
              "'.");
            if(Character.isLetter(issueString.charAt(pos)))
              acceptor.accept(
                issue,
                "Change first character to lower case",
                builder.toString(),
                null,
                new ReplacingModification(
                  issue.getOffset() + pos, 1, Character.toString(Character.toLowerCase(c))));
          }
          else {
            if(c == '_') {
              int count = 0;
              for(int i = pos; i < issueString.length() && issueString.charAt(i) == '_'; i++)
                count++;

              acceptor.accept(
                issue, "Remove the leading underscore", "Removes all leading underscores.", null,
                new ReplacingModification(issue.getOffset() + pos, count, ""));
            }
            else if(Character.isDigit(c)) {
              int count = 0;
              for(int i = pos; i < issueString.length() && Character.isDigit(issueString.charAt(i)); i++)
                count++;
              acceptor.accept(
                issue, "Remove the leading digits", "Removes all leading digits", null,
                new ReplacingModification(issue.getOffset() + pos, count, ""));

            }
            // ? insert 'a' ? (stupid, but perhaps better than nothing)
            acceptor.accept(
              issue, "Insert an 'a' before first character.",
              "Inserts the lower case letter 'a' before the first character", null,
              new ReplacingModification(issue.getOffset() + pos, 0, "a"));

          }
        }
      }
    });
  }

  @Fix(IPPDiagnostics.ISSUE__DQ_STRING_NOT_REQUIRED)
  public void changeToSQString(final Issue issue, final IssueResolutionAcceptor acceptor) {
    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);
    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {

        String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());
        StringBuilder replacement = new StringBuilder();
        replacement.append("'");
        replacement.append(escapeChar(issueString.substring(1, issueString.length() - 1), '\''));
        replacement.append("'");

        acceptor.accept(issue, "Replace with single quoted string", "Changes \" to '", null, //
          new ReplacingModification(issue.getOffset(), issueString.length(), replacement.toString()));
      }
    });
  }

  @Fix(IPPDiagnostics.ISSUE__DQ_STRING_NOT_REQUIRED_VAR)
  public void changeToVariable(final Issue issue, final IssueResolutionAcceptor acceptor) {
    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);
    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {

        String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());

        acceptor.accept(issue, "Replace with variable", "Replace string with " + issue.getData()[0], null, //
          new ReplacingModification(issue.getOffset(), issueString.length(), issue.getData()[0]));
      }
    });
  }

  @Fix(IPPDiagnostics.ISSUE__ENSURE_NOT_FIRST)
  public void ensureNotFirst(final Issue issue, final IssueResolutionAcceptor acceptor) {

    acceptor.accept(
      issue, "Move ensure first.", "Moves the ensure first among the set attributes", null,
      new ISemanticModification() {

        @Override
        public void apply(EObject element, IModificationContext context) throws Exception {
          AttributeOperations aos = (AttributeOperations) element.eContainer();
          aos.getAttributes().move(0, (AttributeOperation) element);
        }
      });
  }

  private String escapeChar(String s, char x) {
    StringBuilder result = new StringBuilder();
    boolean nextIsEscaped = false;
    for(int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);
      if(c == '\\') {
        nextIsEscaped = !nextIsEscaped;
      }
      else if(c == x) {
        if(!nextIsEscaped)
          result.append('\\');
        nextIsEscaped = false;
      }
      else {
        nextIsEscaped = false;
      }
      result.append(c);
    }
    return result.toString();
  }

  @Fix(IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE_PROP)
  public void findClosestClassName(final Issue issue, IssueResolutionAcceptor acceptor) {
    proposeDataAsChangeTo(issue, acceptor);
  }

  @Fix(IPPDiagnostics.ISSUE__UNKNOWN_FUNCTION_REFERENCE_PROP)
  public void findClosestFunction(final Issue issue, IssueResolutionAcceptor acceptor) {
    proposeDataAsChangeTo(issue, acceptor);
  }

  @Fix(IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_PROPERTY_PROP)
  public void findClosestParameters(final Issue issue, IssueResolutionAcceptor acceptor) {
    proposeDataAsChangeTo(issue, acceptor);
  }

  @Fix(IPPDiagnostics.ISSUE_RIGHT_TO_LEFT_RELATIONSHIP)
  public void fixRightToLeftRelationsip(final Issue issue, final IssueResolutionAcceptor acceptor) {
    if(issue.getLength() > 2 || issue.getData() == null || issue.getData().length != 2 ||
        "false".equals(issue.getData()[1]))
      return; // can't fix it

    relationshipExpressionFixer.get().fixRightToLeftRelationsip(issue, acceptor);

  }

  @Fix(IPPDiagnostics.ISSUE__HYPHEN_IN_NAME)
  public void hyphenInName(final Issue issue, final IssueResolutionAcceptor acceptor) {
    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);
    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {
        String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());
        String replacementString = issueString.replaceAll("-", "_");

        acceptor.accept(
          issue, "Change to '" + replacementString + "'", "Changes all '-' to '_' in the name", null, //
          new ReplacingModification(issue.getOffset(), issue.getLength(), replacementString));

        replacementString = issueString.replaceAll("-", "");
        acceptor.accept(issue, "Change to '" + replacementString + "'", "Removes all '-' from name", null, //
          new ReplacingModification(issue.getOffset(), issue.getLength(), replacementString));
      }
    });
  }

  @Fix(IPPDiagnostics.ISSUE__MISSING_COMMA)
  public void insertMissingComma(final Issue issue, IssueResolutionAcceptor acceptor) {
    acceptor.accept(
      issue, "Insert missing comma", "Insert missing comma", null, new ReplacingModification(
        issue.getOffset() + 1, 0, ","));
  }

  @Fix(IPPDiagnostics.ISSUE__INTERPOLATED_HYPHEN)
  public void interpolatedHyphen(final Issue issue, final IssueResolutionAcceptor acceptor) {
    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);
    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {
        EObject varExpr = state.getEObject(issue.getUriToProblem().fragment());
        if(!(varExpr instanceof DoubleQuotedString))
          return; // something is wrong

        // VariableTE varTE = (VariableTE) varExpr;
        // ICompositeNode node = NodeModelUtils.getNode(varTE);

        // b) ${aaa}-bbb - i.e. the 2.6 way
        //
        String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());
        boolean dollarVar = issueString.startsWith("$");
        if(dollarVar)
          issueString = issueString.substring(1);

        // --a)
        StringBuilder builder = new StringBuilder();
        builder.append("${");
        builder.append(issueString);
        builder.append("}");
        acceptor.accept(
          issue, "Change to '" + builder.toString() + "'",
          "Enclose in { } to prevent '-' from being included in variable name", null, //
          new ReplacingModification(issue.getOffset(), issue.getLength(), builder.toString()));

        // // --b)
        // builder = new StringBuilder();
        // builder.append("${");
        // int hyphenPos = issue.getOffset() - node.getOffset();
        // builder.append(node.getText().substring(1, hyphenPos));
        // builder.append("}");
        // acceptor.accept(issue, "Change to '" + builder.toString() + "'", "Change to 2.6 style", null, //
        // new ReplacingModification(node.getOffset(), hyphenPos, builder.toString()));
      }
    });

  }

  @Fix(IPPDiagnostics.ISSUE__NOT_NAME_OR_REF)
  public void makeAllSegmentsSameCase(final Issue issue, IssueResolutionAcceptor acceptor) {
    String data[] = issue.getData();
    if(data == null || data.length != 1)
      return;
    QualifiedName fqn = getQualifiedNameConverter().toQualifiedName(data[0]);
    if(fqn.getSegmentCount() < 2)
      return;
    int upper = 0;
    int lower = 0;
    for(int i = 0; i < fqn.getSegmentCount(); i++) {
      String s = fqn.getSegment(i);
      if(s.length() > 0) {
        if(Character.isUpperCase(s.charAt(0)))
          upper++;
        else if(Character.isLowerCase(s.charAt(0)))
          lower++;
      }
    }
    // if all have same case, or if some where not letters
    if(upper + lower != fqn.getSegmentCount() || upper == 0 || lower == 0)
      return; // some other "unfixable" problem

    String[] segments = fqn.getSegments().toArray(new String[0]);
    for(int i = 0; i < segments.length; i++)
      segments[i] = toInitialCase(segments[i], true);
    QualifiedName upperCaseName = QualifiedName.create(segments);
    for(int i = 0; i < segments.length; i++)
      segments[i] = toInitialCase(segments[i], false);
    QualifiedName lowerCaseName = QualifiedName.create(segments);

    String tmp = getQualifiedNameConverter().toString(upperCaseName);
    acceptor.accept(issue, "Make all segments start with upper case", //
      "Change the name to '" + tmp + "'", null, new ReplacingModification(
        issue.getOffset(), issue.getLength(), tmp));
    tmp = getQualifiedNameConverter().toString(lowerCaseName);
    acceptor.accept(issue, "Make all segments start with lower case", //
      "Change the name to '" + tmp + "'", null, new ReplacingModification(
        issue.getOffset(), issue.getLength(), tmp));
  }

  @Fix(IPPDiagnostics.ISSUE__RESOURCE_AMBIGUOUS_REFERENCE)
  public void makeReferenceAbsolute(final Issue issue, IssueResolutionAcceptor acceptor) {
    String data[] = issue.getData();
    if(data == null)
      return;
    for(String proposal : data) {
      acceptor.accept(issue, "Replace with '" + proposal + "'", //
        "Change the reference to" + (proposal.startsWith("::")
            ? " the absolute: \n"
            : ": \n") + proposal, null, new ReplacingModification(
          issue.getOffset(), issue.getLength(), proposal));
    }
  }

  @Fix(IPPDiagnostics.ISSUE__STRING_BOOLEAN)
  public void makeStringBoolean(final Issue issue, final IssueResolutionAcceptor acceptor) {
    String booleanText = issue.getData()[0];
    acceptor.accept(
      issue, "Change to boolean " + booleanText,
      "A string is always true in boolean sense.\nChange to a real boolean value.", null, //
      new ReplacingModification(issue.getOffset(), issue.getLength(), booleanText));
  }

  private QualifiedName nameOfScope(EObject target) {
    if(target instanceof PuppetManifest)
      return converter.toQualifiedName("::"); // global scope

    QualifiedName scopeName = fqnProvider.getFullyQualifiedName(target);

    // the target happens to be a scope
    if(scopeName != null)
      return scopeName;
    return nameOfScope(target.eContainer());

  }

  private void proposeDataAsChangeTo(final Issue issue, IssueResolutionAcceptor acceptor) {
    String data[] = issue.getData();
    if(data == null || data.length < 1)
      return;

    // Include an ugly number to get them sorted in the correct order
    int proposalNbr = 1;
    for(String proposal : data) {
      String intString = Integer.toString(proposalNbr++);
      if(data.length > 9 && intString.length() < 2)
        intString = "0" + intString;
      acceptor.accept(issue, intString + ". Change to '" + proposal + "'", //
        "Change to (guessed value) '" + proposal + "'", null, new ReplacingModification(
          issue.getOffset(), issue.getLength(), proposal, true));
    }
  }

  @Fix(IPPDiagnostics.ISSUE__EMPTY_STATEMENT)
  public void removeEmptyStatement(final Issue issue, final IssueResolutionAcceptor acceptor) {
    acceptor.accept(issue, "Remove ';'", "The ';' is not needed, it creates an empty statement.", null, //
      new ReplacingModification(issue.getOffset(), issue.getLength(), ""));
  }

  @Fix(IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION_STRING_OK)
  public void surroundExprWithSingleQuote(final Issue issue, IssueResolutionAcceptor acceptor) {

    acceptor.accept(
      issue, "Quote expression", "Surround expression with single quotes", null,
      new SurroundWithTextModification(issue.getOffset(), issue.getLength(), "'"));
  }

  @Fix(IPPDiagnostics.ISSUE__UNSUPPORTED_EXPRESSION_STRING_OK)
  public void surroundWithInterpolation(final Issue issue, IssueResolutionAcceptor acceptor) {

    acceptor.accept(
      issue, "Interpolate expression", "Surround expression with '\"${', '}\"' ", null,
      new SurroundWithTextModification(issue.getOffset(), issue.getLength(), "\"${", "}\""));
  }

  @Fix(IPPDiagnostics.ISSUE__UNQUOTED_QUALIFIED_NAME)
  public void surroundWithSingleQuote(final Issue issue, IssueResolutionAcceptor acceptor) {

    acceptor.accept(issue, "Quote name", "Replace name with quoted name.", null, new SurroundWithTextModification(
      issue.getOffset(), issue.getLength(), "'"));
  }

  @Fix(IPPDiagnostics.ISSUE__UNKNOWN_VARIABLE)
  public void unknownVariable(final Issue issue, final IssueResolutionAcceptor acceptor) {
    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);

    final boolean[] unqualified = new boolean[1];
    unqualified[0] = false;

    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {
        EObject varExpr = state.getEObject(issue.getUriToProblem().fragment());
        if(!(varExpr instanceof VariableExpression || varExpr instanceof VariableTE || varExpr instanceof LiteralNameOrReference))
          return; // something is wrong

        if(issue.getOffset() < 0)
          return; // something is wrong (while editing)

        String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());
        boolean dollarVar = issueString.startsWith("$");
        if(dollarVar)
          issueString = issueString.substring(1);
        QualifiedName fqn = converter.toQualifiedName(issueString);
        if(fqn.getSegmentCount() > 1) {
          ppFinder.configure(varExpr);
          String[] proposals = proposer.computeProposals(issueString, //
            ppFinder.getExportedDescriptions(), //
            searchPathProvider.get(varExpr.eResource()), PARAMS_AND_VARIABLES);
          for(String s : proposals)
            acceptor.accept(issue, "Change to '$" + s + "'", "Did you mean '$" + s + "'", null, //
              new ReplacingModification(issue.getOffset() + (dollarVar
                  ? 1
                  : 0), issueString.length(), s));
        }
        else {
          unqualified[0] = true;
        }
      }
    });
    // if it was unqualified
    if(unqualified[0])
      unqualifiedVariable(issue, acceptor);
  }

  @Fix(IPPDiagnostics.ISSUE__UNQUALIFIED_VARIABLE)
  public void unqualifiedVariable(final Issue issue, final IssueResolutionAcceptor acceptor) {

    final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
      issue);
    final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
    xtextDocument.readOnly(new IUnitOfWork.Void<XtextResource>() {
      @Override
      public void process(XtextResource state) throws Exception {
        EObject varExpr = state.getEObject(issue.getUriToProblem().fragment());
        if(!(varExpr instanceof VariableExpression || varExpr instanceof VariableTE || varExpr instanceof LiteralNameOrReference))
          return; // something is wrong

        String issueString = xtextDocument.get(issue.getOffset(), issue.getLength());
        boolean dollarVar = issueString.startsWith("$");
        if(dollarVar)
          issueString = issueString.substring(1);

        // --GLOBAL NAME
        acceptor.accept(issue, "Change to '$::" + issueString + "'", "Change to '$" + issueString +
            "' in global scope", null, new ReplacingModification(issue.getOffset() + (dollarVar
            ? 1
            : 0), issueString.length(), "::" + issueString));

        // --NAME IN THIS SCOPE - AND OUTER
        // (Propose existing names in this and outer scopes)
        QualifiedName nameOfScope = nameOfScope(varExpr);
        if(nameOfScope.getSegmentCount() < 1)
          return; // it is in global scope (which was already proposed)

        ppFinder.configure(varExpr);
        for(IEObjectDescription desc : ppFinder.findVariables(varExpr, nameOfScope.append(issueString), null).getAdjusted()) {
          String nameInScopeString = converter.toString(desc.getName());
          String foundNameOfScope = converter.toString(desc.getName().skipLast(1));
          String scopeType = desc.getName().skipLast(1).equals(nameOfScope)
              ? "current"
              : "inhertied";
          acceptor.accept(
            issue, "Change to '$" + nameInScopeString + "'", "Change to '$" + issueString + "' in the " +
                scopeType + " scope:\n '" + foundNameOfScope + "'", null, new ReplacingModification(
              issue.getOffset() + (dollarVar
                  ? 1
                  : 0), issueString.length(), nameInScopeString));
        }
        // String scopeType = "current";

        for(QualifiedName qn = nameOfScope.skipLast(1); qn.getSegmentCount() > 0; qn = qn.skipLast(1)) {
          QualifiedName nameInScope = qn.append(issueString);
          String nameInScopeString = converter.toString(nameInScope);
          // TODO: Only propose if this name exists
          // configure for lookup of things
          if(ppFinder.findVariables(varExpr, nameInScopeString, null).getAdjusted().size() > 0)
            acceptor.accept(
              issue, "Change to '$" + converter.toString(nameInScope) + "'", "Change to '$" +
                  issueString + "' in the outer scope:\n '" + converter.toString(qn) + "'", null,
              new ReplacingModification(issue.getOffset() + (dollarVar
                  ? 1
                  : 0), issueString.length(), nameInScopeString));
        }
        // --VARIABLE OR PARAMETER IN A SUPERCLASS
        List<IEObjectDescription> classes = ppFinder.findHostClasses(
          varExpr, converter.toString(nameOfScope), null).getAdjusted();
        if(classes.size() > 0) {
          // ignore ambiguities, just pick the first
          // TODO: Complete PPFinder to search for variables with a search strategy exact, allscopes, allscopesStartsWith
        }
      }
    });
  }

  @Fix(IPPDiagnostics.ISSUE__UNQUOTED_INTERPOLATION)
  public void unquotedInterpolation(final Issue issue, final IssueResolutionAcceptor acceptor) {

    // "${x}
    acceptor.accept(
      issue, "Surround with double quotes", "Places the unquoted interpolation in a string", null,
      new SurroundWithTextModification(issue.getOffset(), issue.getLength(), "\"", "\""));

    // $x
    acceptor.accept(
      issue, "Change to regular variable reference", "Removes the '{' and '}'", null, new IModification() {

        @Override
        public void apply(IModificationContext context) throws Exception {
          IXtextDocument doc = context.getXtextDocument();
          doc.replace(
            issue.getOffset(), issue.getLength(),
            "$" + doc.get(issue.getOffset() + 2, issue.getLength() - 3));
        }
      });

    // $x ? { undef => '', default => $x }
    acceptor.accept(
      issue, "Change to selector that makes undef empty string", "$x ? {undef => '', default => $x }", null,
      new IModification() {

        @Override
        public void apply(IModificationContext context) throws Exception {
          IXtextDocument doc = context.getXtextDocument();
          String varName = "$" + doc.get(issue.getOffset() + 2, issue.getLength() - 3);
          StringBuilder builder = new StringBuilder();
          builder.append(varName);
          builder.append(" ? {");
          builder.append(" undef => '', default => ");
          builder.append(varName);
          builder.append("}");
          doc.replace(issue.getOffset(), issue.getLength(), builder.toString());
        }
      });
  }
}
TOP

Related Classes of com.puppetlabs.geppetto.pp.dsl.ui.quickfix.PPQuickfixProvider

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.