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

au.com.integradev.delphi.checks.CommentedOutCodeCheck 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 static java.util.regex.Pattern.compile;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.check.Rule;
import org.sonar.plugins.communitydelphi.api.ast.DelphiAst;
import org.sonar.plugins.communitydelphi.api.check.DelphiCheck;
import org.sonar.plugins.communitydelphi.api.check.DelphiCheckContext;
import org.sonar.plugins.communitydelphi.api.check.FilePosition;
import org.sonar.plugins.communitydelphi.api.token.DelphiToken;
import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;

@DeprecatedRuleKey(ruleKey = "CommentedOutCodeRule", repositoryKey = "delph")
@Rule(key = "CommentedOutCode")
public class CommentedOutCodeCheck extends DelphiCheck {
  private static final Logger LOG = LoggerFactory.getLogger(CommentedOutCodeCheck.class);
  private static final String MESSAGE =
      "This block of commented-out lines of code should be removed.";

  private static final long REGEX_TIMEOUT_PER_LINE_MILLIS = 250;

  private static final String IDENTIFIER = "[A-Z_][A-Z0-9_]*";
  private static final String PRIMARY_EXPRESSION =
      "(inherited)?\\s*" + IDENTIFIER + "(\\^|(\\.(" + IDENTIFIER + ")|<.+>|(\\[.*])|(\\(.*\\))))*";
  private static final String ARGUMENT_LIST_EOL = "((\\(((\\(.*\\))|[^)])*\\s*,)\\s*$)";
  private static final String COMMENT_OR_WHITESPACE = "(\\s|//.*|\\{(.*?)}|\\(\\*(.*?)\\*\\))";

  private static final String TYPE_DECLARATION_REGEX =
      "(?i)^\\s*"
          + IDENTIFIER
          + "\\s*=\\s*"
          + "(packed)?"
          + "(array|set|file|class|interface|dispinterface|object"
          + "|record|reference|function|procedure|\\^|type|\\()";

  private static final String VAR_DECLARATION_REGEX =
      "(?i)^\\s*"
          + IDENTIFIER
          + "(\\s*,\\s*"
          + IDENTIFIER
          + ")*"
          + "\\s*:\\s*"
          + IDENTIFIER
          + "\\s*;";

  private static final String CONST_DECLARATION_REGEX =
      "(?i)^\\s*" + PRIMARY_EXPRESSION + "\\s*=\\s*.*;$";

  private static final String PROPERTY_DECLARATION_REGEX =
      "(?i)^\\s*property\\s*" + IDENTIFIER + "\\s*:\\s*" + IDENTIFIER;

  private static final String ROUTINE_HEADER_REGEX =
      "(?i)^\\s*(function|procedure|constructor|destructor)\\s*"
          + IDENTIFIER
          + "((\\."
          + IDENTIFIER
          + ")|<.+>)*"
          + "(((\\(.*\\))?(:.*)?\\s*;)|"
          + ARGUMENT_LIST_EOL
          + ")";

  private static final String COMPILER_DIRECTIVE_REGEX = "(?i)\\{\\$" + IDENTIFIER + ".*}";

  private static final String PRIMARY_EXPRESSION_STATEMENT_REGEX =
      "(?i)^\\s*"
          + PRIMARY_EXPRESSION
          + "("
          + COMMENT_OR_WHITESPACE
          + "*;"
          + COMMENT_OR_WHITESPACE
          + "*$|"
          + ARGUMENT_LIST_EOL
          + ")";

  private static final String ASSIGN_STATEMENT_REGEX =
      "(?i)^\\s*" + PRIMARY_EXPRESSION + "\\s*:=.*;$";

  private static final String IF_STATEMENT_REGEX =
      "(?i)^\\s*(else)?\\s*if"
          + "(?!\\s+"
          + IDENTIFIER
          + "\\s(?!(and|or|not|in|is|as|\\+|-|\\*|/|<|>|<>|<=|>=|\\(|@|xor|div|mod|shl|shr)\\b))"
          + "\\s+.*\\b((then\\s*(begin|(.*(else.*)?;))?)|and|or)\\s*$";

  private static final String ELSE_STATEMENT_REGEX = "(?i)^\\s*else(\\s*begin)?\\s*$";

  private static final String FOR_STATEMENT_REGEX =
      "(?i)^\\s*for.*\\b(in|to|downto)\\b.*(do\\s*(begin)?)\\s*$";

  private static final String WITH_STATEMENT_REGEX = "(?i)^\\s*with.*\\bdo\\b\\s*(begin)?\\s*$";

  private static final String RAISE_STATEMENT_REGEX = "(?i)^\\s*raise.*\\s*[(|;]\\s*$";

  private static final String VAR_SECTION_REGEX = "(?i)^\\s*(var|const)\\s*$";

  private static final String BEGIN_END_REGEX = "(?i)^\\s*(begin|end;?)\\s*$";

  private static final String VISIBILITY_SECTION_REGEX =
      "(?i)^\\s*(strict)?\\s*(published|public|protected|private)\\s*$";

  private static final String PARENTHESIZED_CAST_EXPRESSION =
      "\\(" + PRIMARY_EXPRESSION + "\\s*as\\s*" + PRIMARY_EXPRESSION + "\\)";

  private static final Set CODE_PATTERNS =
      Set.of(
          compile(PRIMARY_EXPRESSION_STATEMENT_REGEX),
          compile(ASSIGN_STATEMENT_REGEX),
          compile(ROUTINE_HEADER_REGEX),
          compile(BEGIN_END_REGEX),
          compile(TYPE_DECLARATION_REGEX),
          compile(VAR_DECLARATION_REGEX),
          compile(CONST_DECLARATION_REGEX),
          compile(PROPERTY_DECLARATION_REGEX),
          compile(COMPILER_DIRECTIVE_REGEX),
          compile(IF_STATEMENT_REGEX),
          compile(ELSE_STATEMENT_REGEX),
          compile(FOR_STATEMENT_REGEX),
          compile(WITH_STATEMENT_REGEX),
          compile(RAISE_STATEMENT_REGEX),
          compile(VAR_SECTION_REGEX),
          compile(VISIBILITY_SECTION_REGEX),
          compile(PARENTHESIZED_CAST_EXPRESSION));

  private static final Pattern NEW_LINE_DELIMITER = compile("\r\n?|\n");

  @Override
  public DelphiCheckContext visit(DelphiAst ast, DelphiCheckContext context) {
    List commentedOutCodeLines = new ArrayList<>();
    for (DelphiToken comment : ast.getComments()) {
      commentedOutCodeLines.addAll(handleComment(comment));
    }

    // Greedy algorithm to split lines on blocks and to report only one violation per block
    Collections.sort(commentedOutCodeLines);
    int prev = Integer.MIN_VALUE;
    for (Integer commentedOutCodeLine : commentedOutCodeLines) {
      if (prev + 1 < commentedOutCodeLine) {
        context
            .newIssue()
            .onFilePosition(FilePosition.atLineLevel(commentedOutCodeLine))
            .withMessage(MESSAGE)
            .report();
      }
      prev = commentedOutCodeLine;
    }

    return context;
  }

  private List handleComment(DelphiToken comment) {
    List commentedOutCodeLines = new ArrayList<>();
    String commentString = extractCommentString(comment);
    List lines = Splitter.on(NEW_LINE_DELIMITER).splitToList(commentString);
    for (int i = 0; i < lines.size(); ++i) {
      String line = lines.get(i);
      if (isLineOfCode(line)) {
        // Mark all remaining lines from this comment as a commented out lines of code
        for (int j = i; j < lines.size(); j++) {
          commentedOutCodeLines.add(comment.getBeginLine() + j);
        }
        break;
      }
    }
    return commentedOutCodeLines;
  }

  private static String extractCommentString(DelphiToken comment) {
    String image = comment.getImage();
    if (image.startsWith("//")) {
      return image.substring(2);
    } else if (image.startsWith("{")) {
      return image.substring(1, image.length() - 1);
    } else {
      return image.substring(2, image.length() - 2);
    }
  }

  private boolean isLineOfCode(String line) {
    CharSequence input = createRegexInput(line);
    try {
      for (Pattern pattern : CODE_PATTERNS) {
        if (pattern.matcher(input).find()) {
          return true;
        }
      }
    } catch (RegexTimeoutException e) {
      LOG.warn("Regex timed out after {}ms on input: {}", REGEX_TIMEOUT_PER_LINE_MILLIS, line, e);
    } catch (StackOverflowError e) {
      LOG.warn("Stack overflow on input: {}", line, e);
    }
    return false;
  }

  @VisibleForTesting
  CharSequence createRegexInput(CharSequence sequence) {
    long deadline = System.currentTimeMillis() + REGEX_TIMEOUT_PER_LINE_MILLIS;
    return new RegexTimeoutCharSequence(sequence, deadline);
  }

  @VisibleForTesting
  static class RegexTimeoutCharSequence implements CharSequence {
    private final CharSequence sequence;
    private final long deadline;

    public RegexTimeoutCharSequence(CharSequence sequence, long deadline) {
      super();
      this.sequence = sequence;
      this.deadline = deadline;
    }

    @Override
    public char charAt(int index) {
      if (System.currentTimeMillis() > deadline) {
        throw new RegexTimeoutException();
      }
      return sequence.charAt(index);
    }

    @Override
    public int length() {
      return sequence.length();
    }

    @Override
    public CharSequence subSequence(int start, int end) {
      return new RegexTimeoutCharSequence(sequence.subSequence(start, end), deadline);
    }

    @Override
    public String toString() {
      return sequence.toString();
    }
  }

  private static class RegexTimeoutException extends RuntimeException {}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy