<commit_before>/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jetbrains.python.codeInsight.intentions;

import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.BiPredicate;

/**
 * User: catherine
 *
 * Intention to convert variadic parameter(s) to normal
 * For instance,
 *
 * from:
 * def foo(**kwargs):
 *   doSomething(kwargs['foo'])
 *
 * to:
 * def foo(foo, **kwargs):
 *   doSomething(foo)
 *
 */
public class ConvertVariadicParamIntention extends PyBaseIntentionAction {

  @Override
  @NotNull
  public String getText() {
    return PyBundle.message("INTN.convert.variadic.param");
  }

  @Override
  @NotNull
  public String getFamilyName() {
    return PyBundle.message("INTN.convert.variadic.param");
  }

  @Override
  public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
    if (!(file instanceof PyFile)) {
      return false;
    }

    final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
    final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);

    if (function != null) {
      if (LanguageLevel.forElement(function).isPython2() && function.getParameterList().hasPositionalContainer()) {
        return false;
      }

      final boolean caretInParameterList = PsiTreeUtil.isAncestor(function.getParameterList(), element, true);

      final Set<PyReferenceExpression> references = new HashSet<>(findKeywordContainerUsages(function));
      boolean available = false;

      for (PyCallExpression call : findKeywordContainerCalls(function)) {
        final PyExpression firstArgument = ArrayUtil.getFirstElement(call.getArguments());
        final String firstArgumentValue = PyStringLiteralUtil.getStringValue(firstArgument);
        if (firstArgumentValue != null &&
            PyNames.isIdentifier(firstArgumentValue) &&
            (caretInParameterList || PsiTreeUtil.isAncestor(call, element, true))) {
          available = true;
        }
        //noinspection SuspiciousMethodCalls
        references.remove(call.getReceiver(null));
      }

      for (PySubscriptionExpression subscription : findKeywordContainerSubscriptions(function)) {
        final PyExpression indexExpression = subscription.getIndexExpression();
        final String indexValue = PyStringLiteralUtil.getStringValue(indexExpression);
        if (indexValue != null &&
            PyNames.isIdentifier(indexValue) &&
            (caretInParameterList || PsiTreeUtil.isAncestor(subscription, element, true))) {
          available = true;
        }
        //noinspection SuspiciousMethodCalls
        references.remove(subscription.getOperand());
      }

      return available && references.isEmpty();
    }

    return false;
  }

  @Nullable
  private static PyParameter getKeywordContainer(@NotNull PyParameterList parameterList) {
    return StreamEx
      .of(parameterList.getParameters())
      .select(PyNamedParameter.class)
      .findFirst(PyNamedParameter::isKeywordContainer)
      .orElse(null);
  }

  @Override
  public void doInvoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
    final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
    final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);

    if (function != null) {
      replaceKeywordContainerSubscriptions(function, project);
      replaceKeywordContainerCalls(function, project);
    }
  }

  @NotNull
  private static List<PyReferenceExpression> findKeywordContainerUsages(@NotNull PyFunction function) {
    final BiPredicate<PsiElement, PyParameter> referenceToKeywordContainer =
      (e, container) -> e instanceof PyReferenceExpression && ((PyReferenceExpression)e).getReference().isReferenceTo(container);

    return findKeywordContainerUsages(function, referenceToKeywordContainer);
  }

  @NotNull
  private static List<PySubscriptionExpression> findKeywordContainerSubscriptions(@NotNull PyFunction function) {
    return findKeywordContainerUsages(function, ConvertVariadicParamIntention::isKeywordContainerSubscription);
  }

  @NotNull
  private static List<PyCallExpression> findKeywordContainerCalls(@NotNull PyFunction function) {
    return findKeywordContainerUsages(function, ConvertVariadicParamIntention::isKeywordContainerCall);
  }

  private static void replaceKeywordContainerSubscriptions(@NotNull PyFunction function, @NotNull Project project) {
    final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);

    for (PySubscriptionExpression subscription : findKeywordContainerSubscriptions(function)) {
      Optional
        .ofNullable(subscription.getIndexExpression())
        .map(indexExpression -> PyUtil.as(indexExpression, PyStringLiteralExpression.class))
        .map(PyStringLiteralExpression::getStringValue)
        .filter(PyNames::isIdentifier)
        .ifPresent(
          indexValue -> {
            final PyExpression parameter = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), indexValue);

            insertParameter(function.getParameterList(), parameter, false, elementGenerator);
            subscription.replace(parameter);
          }
        );
    }
  }

  private static void replaceKeywordContainerCalls(@NotNull PyFunction function, @NotNull Project project) {
    final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);

    for (PyCallExpression call : findKeywordContainerCalls(function)) {
      Optional
        .of(call.getArguments())
        .map(ArrayUtil::getFirstElement)
        .map(firstArgument -> PyUtil.as(firstArgument, PyStringLiteralExpression.class))
        .map(PyStringLiteralExpression::getStringValue)
        .filter(PyNames::isIdentifier)
        .ifPresent(
          indexValue -> {
            final PyNamedParameter parameter = createParameter(elementGenerator, call, indexValue);
            if (parameter != null) {
              final PyExpression parameterUsage = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), indexValue);

              insertParameter(function.getParameterList(), parameter, parameter.hasDefaultValue(), elementGenerator);
              call.replace(parameterUsage);
            }
          }
        );
    }
  }

  @NotNull
  private static <T> List<T> findKeywordContainerUsages(@NotNull PyFunction function,
                                                        @NotNull BiPredicate<PsiElement, PyParameter> usagePredicate) {
    final PyParameter keywordContainer = getKeywordContainer(function.getParameterList());

    if (keywordContainer != null) {
      final List<T> result = new ArrayList<>();
      final Stack<PsiElement> stack = new Stack<>();

      for (PyStatement statement : function.getStatementList().getStatements()) {
        stack.push(statement);

        while (!stack.isEmpty()) {
          final PsiElement element = stack.pop();

          if (usagePredicate.test(element, keywordContainer)) {
            //noinspection unchecked
            result.add((T)element);
          }
          else {
            for (PsiElement child : element.getChildren()) {<commit_msg>Reduce code duplication (PY-2264)<commit_after>/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jetbrains.python.codeInsight.intentions;

import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.BiPredicate;

/**
 * User: catherine
 *
 * Intention to convert variadic parameter(s) to normal
 * For instance,
 *
 * from:
 * def foo(**kwargs):
 *   doSomething(kwargs['foo'])
 *
 * to:
 * def foo(foo, **kwargs):
 *   doSomething(foo)
 *
 */
public class ConvertVariadicParamIntention extends PyBaseIntentionAction {

  @Override
  @NotNull
  public String getText() {
    return PyBundle.message("INTN.convert.variadic.param");
  }

  @Override
  @NotNull
  public String getFamilyName() {
    return PyBundle.message("INTN.convert.variadic.param");
  }

  @Override
  public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
    if (!(file instanceof PyFile)) {
      return false;
    }

    final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
    final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);

    if (function != null) {
      if (LanguageLevel.forElement(function).isPython2() && function.getParameterList().hasPositionalContainer()) {
        return false;
      }

      final boolean caretInParameterList = PsiTreeUtil.isAncestor(function.getParameterList(), element, true);

      final Set<PyReferenceExpression> references = new HashSet<>(findKeywordContainerUsages(function));
      boolean available = false;

      for (PyCallExpression call : findKeywordContainerCalls(function)) {
        if ((caretInParameterList || PsiTreeUtil.isAncestor(call, element, true)) && getIndexValueToReplace(call) != null) {
          available = true;
        }
        //noinspection SuspiciousMethodCalls
        references.remove(call.getReceiver(null));
      }

      for (PySubscriptionExpression subscription : findKeywordContainerSubscriptions(function)) {
        if ((caretInParameterList || PsiTreeUtil.isAncestor(subscription, element, true)) && getIndexValueToReplace(subscription) != null) {
          available = true;
        }
        //noinspection SuspiciousMethodCalls
        references.remove(subscription.getOperand());
      }

      return available && references.isEmpty();
    }

    return false;
  }

  @Nullable
  private static PyParameter getKeywordContainer(@NotNull PyParameterList parameterList) {
    return StreamEx
      .of(parameterList.getParameters())
      .select(PyNamedParameter.class)
      .findFirst(PyNamedParameter::isKeywordContainer)
      .orElse(null);
  }

  @Override
  public void doInvoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
    final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
    final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);

    if (function != null) {
      replaceKeywordContainerSubscriptions(function, project);
      replaceKeywordContainerCalls(function, project);
    }
  }

  @NotNull
  private static List<PyReferenceExpression> findKeywordContainerUsages(@NotNull PyFunction function) {
    final BiPredicate<PsiElement, PyParameter> referenceToKeywordContainer =
      (e, container) -> e instanceof PyReferenceExpression && ((PyReferenceExpression)e).getReference().isReferenceTo(container);

    return findKeywordContainerUsages(function, referenceToKeywordContainer);
  }

  @NotNull
  private static List<PySubscriptionExpression> findKeywordContainerSubscriptions(@NotNull PyFunction function) {
    return findKeywordContainerUsages(function, ConvertVariadicParamIntention::isKeywordContainerSubscription);
  }

  @Nullable
  private static String getIndexValueToReplace(@NotNull PySubscriptionExpression subscription) {
    return Optional
      .ofNullable(subscription.getIndexExpression())
      .map(indexExpression -> PyUtil.as(indexExpression, PyStringLiteralExpression.class))
      .map(PyStringLiteralExpression::getStringValue)
      .filter(PyNames::isIdentifier)
      .orElse(null);
  }

  @NotNull
  private static List<PyCallExpression> findKeywordContainerCalls(@NotNull PyFunction function) {
    return findKeywordContainerUsages(function, ConvertVariadicParamIntention::isKeywordContainerCall);
  }

  @Nullable
  private static String getIndexValueToReplace(@NotNull PyCallExpression call) {
    return Optional
      .of(call.getArguments())
      .map(ArrayUtil::getFirstElement)
      .map(firstArgument -> PyUtil.as(firstArgument, PyStringLiteralExpression.class))
      .map(PyStringLiteralExpression::getStringValue)
      .filter(PyNames::isIdentifier)
      .orElse(null);
  }

  private static void replaceKeywordContainerSubscriptions(@NotNull PyFunction function, @NotNull Project project) {
    final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);

    for (PySubscriptionExpression subscription : findKeywordContainerSubscriptions(function)) {
      final String indexValue = getIndexValueToReplace(subscription);
      if (indexValue != null) {
        final PyExpression parameter = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), indexValue);

        insertParameter(function.getParameterList(), parameter, false, elementGenerator);
        subscription.replace(parameter);
      }
    }
  }

  private static void replaceKeywordContainerCalls(@NotNull PyFunction function, @NotNull Project project) {
    final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);

    for (PyCallExpression call : findKeywordContainerCalls(function)) {
      final String indexValue = getIndexValueToReplace(call);
      if (indexValue != null) {
        final PyNamedParameter parameter = createParameter(elementGenerator, call, indexValue);
        if (parameter != null) {
          final PyExpression parameterUsage = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), indexValue);

          insertParameter(function.getParameterList(), parameter, parameter.hasDefaultValue(), elementGenerator);
          call.replace(parameterUsage);
        }
      }
    }
  }

  @NotNull
  private static <T> List<T> findKeywordContainerUsages(@NotNull PyFunction function,
                                                        @NotNull BiPredicate<PsiElement, PyParameter> usagePredicate) {
    final PyParameter keywordContainer = getKeywordContainer(function.getParameterList());

    if (keywordContainer != null) {
      final List<T> result = new ArrayList<>();
      final Stack<PsiElement> stack = new Stack<>();

      for (PyStatement statement : function.getStatementList().getStatements()) {
        stack.push(statement);

        while (!stack.isEmpty()) {
          final PsiElement element = stack.pop();

          if (usagePredicate.test(element, keywordContainer)) {
            //noinspection unchecked
            result.add((T)element);
          }
          else {
            for (PsiElement child : element.getChildren()) {
{'fraction_non_alphanumeric': 0.09403601124603697, 'fraction_numerical': 0.0017945803672907818, 'mean_word_length': 4.0234375, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3638215', 'n_tokens_mistral': 4310, 'n_tokens_neox': 4276, 'n_words': 965}