All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.flex.checks.UnusedFunctionParametersCheck Maven / Gradle / Ivy

/*
 * SonarQube Flex Plugin
 * Copyright (C) 2010-2023 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.flex.checks;

import com.sonar.sslr.api.AstNode;
import com.sonar.sslr.api.AstNodeType;
import com.sonar.sslr.api.Token;
import java.text.MessageFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.flex.FlexCheck;
import org.sonar.flex.FlexGrammar;
import org.sonar.flex.FlexKeyword;
import org.sonar.flex.checks.utils.Function;
import org.sonar.flex.checks.utils.Preconditions;

@Rule(key = "S1172")
public class UnusedFunctionParametersCheck extends FlexCheck {

  private Deque classes = new ArrayDeque<>();

  private static class Scope {
    private final Scope outerScope;
    private final AstNode functionDec;
    private final Map arguments;

    public Scope(Scope outerScope, AstNode functionDec) {
      this.outerScope = outerScope;
      this.functionDec = functionDec;
      this.arguments = new LinkedHashMap<>();
    }

    private void declare(AstNode astNode) {
      Preconditions.checkState(astNode.is(FlexGrammar.IDENTIFIER));
      String identifier = astNode.getTokenValue();
      arguments.put(identifier, 0);
    }

    private void use(String value) {
      Scope scope = this;
      while (scope != null) {
        Integer usage = scope.arguments.get(value);
        if (usage != null) {
          usage++;
          scope.arguments.put(value, usage);
          return;
        }
        scope = scope.outerScope;
      }
    }
  }

  private static final AstNodeType[] FUNCTION_NODES = {FlexGrammar.FUNCTION_DEF, FlexGrammar.FUNCTION_EXPR};
  private Scope currentScope;

  @Override
  public List subscribedTo() {
    List types = new ArrayList<>();
    types.add(FlexGrammar.POSTFIX_EXPR);
    types.add(FlexGrammar.PARAMETERS);
    types.add(FlexGrammar.CLASS_DEF);
    Collections.addAll(types, FUNCTION_NODES);
    return types;
  }

  @Override
  public void visitFile(@Nullable AstNode astNode) {
    currentScope = null;
    classes.clear();
  }

  @Override
  public void visitNode(AstNode astNode) {
    if (astNode.is(FlexGrammar.CLASS_DEF)) {
      classes.push(implementsAnInterface(astNode));
    } else if (astNode.is(FUNCTION_NODES)) {
      // enter new scope
      currentScope = new Scope(currentScope, astNode);

    } else if (currentScope != null && astNode.is(FlexGrammar.PARAMETERS) && astNode.getParent().is(FlexGrammar.FUNCTION_SIGNATURE)) {
      declareInCurrentScope(Function.getParametersIdentifiers(currentScope.functionDec));

    } else if (currentScope != null && astNode.is(FlexGrammar.POSTFIX_EXPR)) {
      AstNode postfixExprChild = astNode.getFirstChild();
      // check if it is not a call to function with same name than the parameter
      if (postfixExprChild.is(FlexGrammar.PRIMARY_EXPR) && postfixExprChild.getNextAstNode().isNot(FlexGrammar.ARGUMENTS)) {
        currentScope.use(getPrimaryExpressionStringValue(postfixExprChild));
      }
    }
  }

  @Override
  public void leaveNode(AstNode astNode) {
    if (astNode.is(FUNCTION_NODES) && isNotAbstract(astNode)) {
      // leave scope
      if (!isExcluded(astNode)) {
        reportUnusedArgument();
      }
      currentScope = currentScope.outerScope;
    } else if (astNode.is(FlexGrammar.CLASS_DEF)) {
      classes.pop();
    }
  }

  private void reportUnusedArgument() {
    int nbUnusedArgs = 0;
    StringBuilder formatBuilder = new StringBuilder("Remove the unused function {0} \"");

    for (Map.Entry entry : currentScope.arguments.entrySet()) {
      if (entry.getValue() == 0) {
        formatBuilder.append(entry.getKey()).append(", ");
        nbUnusedArgs++;
      }
    }

    if (nbUnusedArgs > 0) {
      formatBuilder.replace(formatBuilder.length() - 2, formatBuilder.length(), "\".");
      String message = MessageFormat.format(formatBuilder.toString(), nbUnusedArgs > 1 ? "parameters" : "parameter");
      addIssue(message, currentScope.functionDec);
    }
  }

  private boolean isExcluded(AstNode functionDec) {
    AstNode directives = functionDec
      .getFirstChild(FlexGrammar.FUNCTION_COMMON)
      .getFirstChild(FlexGrammar.BLOCK)
      .getFirstChild(FlexGrammar.DIRECTIVES);

    return isExcludedFunctionDeclaration(functionDec) || isEmpty(directives)
      || containsOnlyThrowStmt(directives) || isInClassImplementingInterface();
  }

  private static Boolean implementsAnInterface(AstNode classDef) {
    AstNode inheritenceNode = classDef.getFirstChild(FlexGrammar.INHERITENCE);
    return inheritenceNode != null && inheritenceNode.getFirstChild().is(FlexKeyword.IMPLEMENTS);
  }

  private boolean isInClassImplementingInterface() {
    return !classes.isEmpty() && classes.peek();
  }

  private static boolean containsOnlyThrowStmt(AstNode directives) {
    List directiveList = directives.getChildren();

    if (directiveList.size() == 1) {
      AstNode directiveKind = directiveList.get(0).getFirstChild().getFirstChild();
      return directiveKind.is(FlexGrammar.THROW_STATEMENT);
    }
    return false;
  }

  private static boolean isEmpty(AstNode directives) {
    return directives.getNumberOfChildren() == 0;
  }

  private void declareInCurrentScope(List identifiers) {
    for (AstNode identifier : identifiers) {
      currentScope.declare(identifier);
    }
  }

  private static boolean isExcludedFunctionDeclaration(AstNode functionDec) {
    return functionDec.is(FlexGrammar.FUNCTION_DEF) && (Function.isOverriding(functionDec) || isEventHandler(functionDec));
  }

  private static boolean isEventHandler(AstNode functionDec) {
    String functionName = functionDec.getFirstChild(FlexGrammar.FUNCTION_NAME).getTokenValue();

    if (functionName.toLowerCase(Locale.ENGLISH).contains("handle") || startsWithOnPreposition(functionName)) {
      AstNode parameters = functionDec
        .getFirstChild(FlexGrammar.FUNCTION_COMMON)
        .getFirstChild(FlexGrammar.FUNCTION_SIGNATURE)
        .getFirstChild(FlexGrammar.PARAMETERS);

      if (parameters != null) {
        AstNode firstParameter = parameters.getFirstChild(FlexGrammar.PARAMETER);

        if (firstParameter != null && firstParameter.getFirstChild(FlexGrammar.TYPED_IDENTIFIER) != null) {
          AstNode firstParameterType = firstParameter
            .getFirstChild(FlexGrammar.TYPED_IDENTIFIER)
            .getFirstChild(FlexGrammar.TYPE_EXPR);
          return firstParameterType != null && firstParameterType.getLastToken().getValue().endsWith("Event");
        }
      }
    }
    return false;
  }

  private static boolean startsWithOnPreposition(String name) {
    return name.startsWith("on") && (name.length() == 2 || name.substring(2, 3).matches("[A-Z]"));
  }

  private static boolean isNotAbstract(AstNode functionDef) {
    return functionDef.getFirstChild(FlexGrammar.FUNCTION_COMMON).getLastChild().is(FlexGrammar.BLOCK);
  }

  private static String getPrimaryExpressionStringValue(AstNode postfixExpr) {
    StringBuilder builder = new StringBuilder();
    for (Token t : postfixExpr.getTokens()) {
      builder.append(t.getValue());
    }
    return builder.toString();
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy