Skip to content

Commit

Permalink
add new rule "Replace DESCRIBE TABLE ... LINES with lines( )" (SAP#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmgrassau committed Mar 7, 2024
1 parent cdd7bc1 commit bfa99ff
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.*;

public abstract class Rule {
public static final int RULE_COUNT = 70;
public static final int RULE_COUNT = 71;
public static final int RULE_GROUP_COUNT = 7;

protected static final String LINE_SEP = ABAP.LINE_SEPARATOR;
Expand Down Expand Up @@ -113,6 +113,7 @@ static Rule[] getAllRules(Profile profile) {
new AddToEtcRule(profile),
new MoveToRule(profile),
new TranslateRule(profile),
new DescribeTableRule(profile),
new AssertEqualsBooleanRule(profile),
new AssertEqualsSubrcRule(profile),
new AssertClassRule(profile),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ public abstract class RuleForCommands extends Rule {

protected boolean skipDeclarationsInsideBeginOf() { return false; }

/** true if a rule only works on commands inside of METHOD, FUNCTION, or FORM,
* and requires this context to be visible (so smaller code snippets must be ignored) */
protected boolean skipOutsideMethodFunctionOrForm() { return false; }

protected RuleForCommands(Profile profile) {
super(profile);
}
Expand All @@ -21,17 +25,34 @@ public void executeOn(Code code, int releaseRestriction) throws UnexpectedSyntax
throw new NullPointerException("code");

boolean skipInsideBeginOf = skipDeclarationsInsideBeginOf();
boolean skipOutsideMethod = skipOutsideMethodFunctionOrForm();

int blockLevel = 0;
boolean isInsideMethod = false; // for a code snippet that only covers a part of a method, this intentionally stays false

Command command = code.firstCommand;

while (command != null) {
commandForErrorMsg = command;

if (skipOutsideMethod) {
// keep track of whether the command is inside a method
if (command.isMethodFunctionOrFormStart()) {
isInsideMethod = true;
} else if (command.isMethodFunctionOrFormEnd()) {
isInsideMethod = false;
} else if (command.isClassDefinitionStart()) {
// a CLASS DEFINITION can be skipped entirely for rules that skip commands outside methods
command = command.getNextSibling();
continue;
}
}

// get the next Command now, in case the current command is removed from the code
Command nextCommand = command.getNext();
int blockLevelDiff = command.getBlockLevelDiff();

if (!isCommandBlocked(command) && (!skipInsideBeginOf || blockLevel == 0)) {
if (!isCommandBlocked(command) && (!skipInsideBeginOf || blockLevel == 0) && (!skipOutsideMethod || isInsideMethod)) {
try {
if (executeOn(code, command, releaseRestriction)) {
code.addRuleUse(this, command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public enum RuleID {
ADD_TO_ETC,
MOVE_TO,
TRANSLATE,
DESCRIBE_TABLE,
// - ASSERTs
ASSERT_EQUALS_BOOLEAN,
ASSERT_EQUALS_SUBRC,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.sap.adt.abapcleaner.rules.commands;

import java.time.LocalDate;
import java.util.ArrayList;

import com.sap.adt.abapcleaner.base.ABAP.SyField;
import com.sap.adt.abapcleaner.parser.Code;
import com.sap.adt.abapcleaner.parser.Command;
import com.sap.adt.abapcleaner.parser.Token;
import com.sap.adt.abapcleaner.parser.TokenSearch;
import com.sap.adt.abapcleaner.programbase.UnexpectedSyntaxAfterChanges;
import com.sap.adt.abapcleaner.rulebase.Profile;
import com.sap.adt.abapcleaner.rulebase.RuleForCommands;
import com.sap.adt.abapcleaner.rulebase.RuleGroupID;
import com.sap.adt.abapcleaner.rulebase.RuleID;
import com.sap.adt.abapcleaner.rulebase.RuleReference;
import com.sap.adt.abapcleaner.rulebase.RuleSource;
import com.sap.adt.abapcleaner.rulehelpers.SyFieldAnalyzer;

public class DescribeTableRule extends RuleForCommands {
private final static RuleReference[] references = new RuleReference[] { new RuleReference(RuleSource.ABAP_CLEANER) };

@Override
public RuleID getID() { return RuleID.DESCRIBE_TABLE; }

@Override
public RuleGroupID getGroupID() { return RuleGroupID.COMMANDS; }

@Override
public String getDisplayName() { return "Replace DESCRIBE TABLE ... LINES with lines( )"; }

@Override
public String getDescription() { return "Replaces DESCRIBE TABLE ... LINES with the built-in function lines( )."; }

@Override
public String getHintsAndRestrictions() { return "Statements cannot be replaced if they use other additions (KIND ... or OCCURS ...) or SY-TFILL / SY-TLENG are evaluated afterwards."; }

@Override
public LocalDate getDateCreated() { return LocalDate.of(2024, 3, 7); }

@Override
public RuleReference[] getReferences() { return references; }

@Override
public RuleID[] getDependentRules() { return new RuleID[] { RuleID.UPPER_AND_LOWER_CASE } ; }

@Override
public String getExample() {
return ""
+ LINE_SEP + " METHOD describe_table_lines."
+ LINE_SEP + " \" the following cases can be replaced with the built-in function lines( ):"
+ LINE_SEP + " DESCRIBE TABLE its_any_table LINES ev_any_line_count."
+ LINE_SEP + " DESCRIBE TABLE its_any_table LINES DATA(lv_any_line_count)."
+ LINE_SEP + " DESCRIBE TABLE its_other_table LINES FINAL(lv_other_line_count)."
+ LINE_SEP
+ LINE_SEP + " \" DESCRIBE TABLE with other additions cannot be replaced"
+ LINE_SEP + " DESCRIBE TABLE it_third_table KIND DATA(lv_kind) LINES lv_any_line_count."
+ LINE_SEP + " DESCRIBE TABLE it_third_table OCCURS FINAL(lv_init_mem_requirement)."
+ LINE_SEP
+ LINE_SEP + " \" DESCRIBE TABLE with any subsequent evaluation of SY-TLENG or SY-TFILL cannot be replaced"
+ LINE_SEP + " DESCRIBE TABLE it_third_table LINES lv_any_line_count."
+ LINE_SEP + " DATA(lv_line_count) = sy-tfill."
+ LINE_SEP + " DATA(lv_line_length_in_bytes) = sy-tleng."
+ LINE_SEP
+ LINE_SEP + " \" here, SY-TLENG is also evaluated in the program flow after DESCRIBE TABLE"
+ LINE_SEP + " \" (but only for the second one, so the first one can be changed)"
+ LINE_SEP + " DO 3 TIMES."
+ LINE_SEP + " IF sy-index = 3."
+ LINE_SEP + " RETURN sy-tleng."
+ LINE_SEP + " ENDIF."
+ LINE_SEP + " DESCRIBE TABLE it_third_table LINES lv_any_line_count."
+ LINE_SEP + " DESCRIBE TABLE it_fourth_table LINES lv_line_count."
+ LINE_SEP + " ENDDO."
+ LINE_SEP + " ENDMETHOD.";
}

public DescribeTableRule(Profile profile) {
super(profile);
initializeConfiguration();
}

@Override
protected boolean skipOutsideMethodFunctionOrForm() {
// ensure that we are inside a METHOD, FUNCTION or FORM, otherwise (esp. if the standalone version gets a small
// code snippet), we cannot be sure that the statement is not indeed followed by an evaluation of SY-TFILL or SY-TLENG
return true;
}

@Override
protected boolean executeOn(Code code, Command command, int releaseRestriction) throws UnexpectedSyntaxAfterChanges {
// From the possible syntax 'DESCRIBE TABLE itab [KIND knd] [LINES lin] [OCCURS n].',
// only process cases that match 'DESCRIBE TABLE itab LINES lin.' (excluding cases with chains, comments, or pragmas)
Token firstToken = command.getFirstToken();
if (!firstToken.matchesOnSiblings(false, "DESCRIBE", "TABLE", TokenSearch.ANY_IDENTIFIER, "LINES"))
return false;
if (firstToken.matchesOnSiblings(true, TokenSearch.ASTERISK, "KIND") || firstToken.matchesOnSiblings(true, TokenSearch.ASTERISK, "OCCURS"))
return false;

// determine the keywords and the table identifier
Token describeKeyword = firstToken;
Token tableKeyword = firstToken.getNextSibling();
Token tableName = tableKeyword.getNextSibling();
Token linesKeyword = tableName.getNextSibling();

// determine the receiving variable, which may also be an inline declaration DATA(...) or FINAL(...)
Token lineCountVarStart = linesKeyword.getNextSibling();
if (lineCountVarStart.isPragmaOrComment())
return false;
Token lineCountVarEnd = lineCountVarStart.getNextSiblingWhileLevelOpener();

// exclude cases where a SY- field is in the write position after LINES, e.g. 'DESCRIBE TABLE lt_any LINES sy-tfill.'
if (lineCountVarStart.isIdentifier() && lineCountVarStart.textStartsWith("sy-"))
return false;

// determine the period (chained statements will not be processed)
Token period = lineCountVarEnd.getNextSibling();
if (!period.isPeriod())
return false;

// ensure that SY-TFILL and SY-TLENG are never used in the program flow following this statement
ArrayList<Command> commandsReadingSyTFill = SyFieldAnalyzer.getSyFieldReadersFor(SyField.TFILL, command);
if (commandsReadingSyTFill != null && commandsReadingSyTFill.size() > 0)
return false;
ArrayList<Command> commandsReadingSyTLeng = SyFieldAnalyzer.getSyFieldReadersFor(SyField.TLENG, command);
if (commandsReadingSyTLeng != null && commandsReadingSyTLeng.size() > 0)
return false;

// transform "DESCRIBE TABLE itab LINES lin." into "lin = lines( itab )."
lineCountVarStart.copyWhitespaceFrom(describeKeyword);
describeKeyword.removeFromCommand();
tableKeyword.removeFromCommand();
tableName.removeFromCommand();
linesKeyword.removeFromCommand();
period.insertLeftSibling(Token.createForAbap(0, 1, "=", period.sourceLineNum), false);
Token linesIdentifier = Token.createForAbap(0, 1, "lines(", period.sourceLineNum);
period.insertLeftSibling(linesIdentifier, false, true);
period.insertLeftSibling(Token.createForAbap(0, 1, ")", period.sourceLineNum), false, true);
tableName.setWhitespace();
linesIdentifier.insertNext(tableName);

code.addRuleUse(this, command);
return true;
}
}
Loading

0 comments on commit bfa99ff

Please sign in to comment.