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

au.com.integradev.delphi.checks.VisibilitySectionOrderCheck Maven / Gradle / Ivy

There is a newer version: 1.12.1
Show newest version
/*
 * Sonar Delphi Plugin
 * Copyright (C) 2019 Integrated Application Development
 *
 * 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  02
 */
package au.com.integradev.delphi.checks;

import com.google.common.collect.Maps;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.sonar.check.Rule;
import org.sonar.plugins.communitydelphi.api.ast.ConstDeclarationNode;
import org.sonar.plugins.communitydelphi.api.ast.ConstSectionNode;
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
import org.sonar.plugins.communitydelphi.api.ast.FieldSectionNode;
import org.sonar.plugins.communitydelphi.api.ast.NameDeclarationNode;
import org.sonar.plugins.communitydelphi.api.ast.Node;
import org.sonar.plugins.communitydelphi.api.ast.RoutineDeclarationNode;
import org.sonar.plugins.communitydelphi.api.ast.StructTypeNode;
import org.sonar.plugins.communitydelphi.api.ast.TypeSectionNode;
import org.sonar.plugins.communitydelphi.api.ast.Visibility.VisibilityType;
import org.sonar.plugins.communitydelphi.api.ast.VisibilityNode;
import org.sonar.plugins.communitydelphi.api.ast.VisibilitySectionNode;
import org.sonar.plugins.communitydelphi.api.check.DelphiCheck;
import org.sonar.plugins.communitydelphi.api.check.DelphiCheckContext;
import org.sonar.plugins.communitydelphi.api.symbol.NameOccurrence;
import org.sonar.plugins.communitydelphi.api.symbol.scope.FileScope;
import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;

@DeprecatedRuleKey(ruleKey = "VisibilitySectionOrderRule", repositoryKey = "delph")
@Rule(key = "VisibilitySectionOrder")
public class VisibilitySectionOrderCheck extends DelphiCheck {
  private static final String MESSAGE = "Move this visibility section.";

  private static final Map VISIBILITY_ORDER =
      Maps.immutableEnumMap(
          Map.of(
              VisibilityType.IMPLICIT_PUBLISHED, 0,
              VisibilityType.STRICT_PRIVATE, 1,
              VisibilityType.PRIVATE, 2,
              VisibilityType.STRICT_PROTECTED, 3,
              VisibilityType.PROTECTED, 4,
              VisibilityType.PUBLIC, 5,
              VisibilityType.PUBLISHED, 6));

  @Override
  public DelphiCheckContext visit(StructTypeNode structTypeNode, DelphiCheckContext context) {
    checkOrder(structTypeNode.getVisibilitySections(), context);
    return super.visit(structTypeNode, context);
  }

  private void checkOrder(
      List visibilitySections, DelphiCheckContext context) {
    if (visibilitySections.size() < 2) {
      return;
    }

    Deque pastSections = new ArrayDeque<>();
    pastSections.add(visibilitySections.get(0));

    for (int i = 1; i < visibilitySections.size(); i++) {
      VisibilitySectionNode thisSection = visibilitySections.get(i);
      VisibilitySectionNode lastSection = pastSections.peek();

      int thisSectionVisibility = VISIBILITY_ORDER.get(thisSection.getVisibility());

      // If this section has a lower visibility than the last section...
      if (lastSection != null
          && VISIBILITY_ORDER.get(lastSection.getVisibility()) > thisSectionVisibility) {
        // Remove excluded sections until a non-excluded one is found
        while (lastSection != null && isExcludedSection(lastSection)) {
          pastSections.pop();
          lastSection = pastSections.peek();
        }

        // If the nearest non-excluded section is higher visibility, report an issue
        if (lastSection != null
            && VISIBILITY_ORDER.get(lastSection.getVisibility()) > thisSectionVisibility) {
          report(context, thisSection);
        }
      }

      pastSections.add(thisSection);
    }
  }

  private void report(DelphiCheckContext context, VisibilitySectionNode visibilitySection) {
    var visibilityNode = getVisibilityNode(visibilitySection);
    reportIssue(context, Objects.requireNonNullElse(visibilityNode, visibilitySection), MESSAGE);
  }

  private static boolean isExcludedSection(VisibilitySectionNode visibilitySection) {
    return visibilitySection.getChildren().stream()
        .anyMatch(VisibilitySectionOrderCheck::hasUsagesInsideType);
  }

  private static boolean hasUsagesInsideType(DelphiNode node) {
    StructTypeNode structNode = node.getFirstParentOfType(StructTypeNode.class);
    return getVisibilitySectionItemUsages(node).stream()
        .anyMatch(usage -> isInsideNode(structNode, usage.getLocation()));
  }

  private static boolean isInsideNode(StructTypeNode structNode, Node node) {
    if (!node.getScope()
        .getEnclosingScope(FileScope.class)
        .equals(structNode.getScope().getEnclosingScope(FileScope.class))) {
      return false;
    }

    boolean afterStart =
        structNode.getBeginLine() < node.getBeginLine()
            || (structNode.getBeginLine() == node.getBeginLine()
                && structNode.getBeginColumn() <= node.getBeginColumn());
    boolean beforeEnd =
        structNode.getEndLine() > node.getEndLine()
            || (structNode.getEndLine() == node.getEndLine()
                && structNode.getEndColumn() >= node.getEndColumn());

    return afterStart && beforeEnd;
  }

  private static List getVisibilitySectionItemUsages(DelphiNode node) {
    if (node instanceof RoutineDeclarationNode) {
      return ((RoutineDeclarationNode) node).getRoutineNameNode().getUsages();
    } else if (node instanceof FieldSectionNode) {
      return ((FieldSectionNode) node)
          .getDeclarations().stream()
              .flatMap(
                  declarationNode ->
                      declarationNode.getDeclarationList().getDeclarations().stream()
                          .map(NameDeclarationNode::getUsages)
                          .flatMap(Collection::stream))
              .collect(Collectors.toList());
    } else if (node instanceof ConstSectionNode) {
      return node.findChildrenOfType(ConstDeclarationNode.class).stream()
          .map(declarationNode -> declarationNode.getNameDeclarationNode().getUsages())
          .flatMap(Collection::stream)
          .collect(Collectors.toList());
    } else if (node instanceof TypeSectionNode) {
      return ((TypeSectionNode) node)
          .getDeclarations().stream()
              .map(typeDecl -> typeDecl.getTypeNameNode().getUsages())
              .flatMap(Collection::stream)
              .collect(Collectors.toList());
    } else {
      return Collections.emptyList();
    }
  }

  private static VisibilityNode getVisibilityNode(VisibilitySectionNode visibilitySectionNode) {
    Node firstChild = visibilitySectionNode.getChild(0);
    if (firstChild instanceof VisibilityNode) {
      return (VisibilityNode) firstChild;
    }
    return null;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy