-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add new rule "Replace CONDENSE with string function" (#36)
- Loading branch information
Showing
6 changed files
with
342 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 176 additions & 0 deletions
176
com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/rules/commands/CondenseRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
package com.sap.adt.abapcleaner.rules.commands; | ||
|
||
import java.time.LocalDate; | ||
|
||
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.IntegrityBrokenException; | ||
import com.sap.adt.abapcleaner.programbase.UnexpectedSyntaxAfterChanges; | ||
import com.sap.adt.abapcleaner.programbase.UnexpectedSyntaxBeforeChanges; | ||
import com.sap.adt.abapcleaner.rulebase.ConfigBoolValue; | ||
import com.sap.adt.abapcleaner.rulebase.ConfigValue; | ||
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.rules.alignment.AlignParametersRule; | ||
|
||
public class CondenseRule extends RuleForCommands { | ||
private final static RuleReference[] references = new RuleReference[] { | ||
new RuleReference(RuleSource.ABAP_STYLE_GUIDE, "Prefer functional to procedural language constructs", "#prefer-functional-to-procedural-language-constructs"), | ||
new RuleReference(RuleSource.ABAP_KEYWORD_DOCU, "CONDENSE", "abapcondense.htm"), | ||
new RuleReference(RuleSource.ABAP_KEYWORD_DOCU, "string_func - condense", "abencondense_functions.htm") }; | ||
|
||
@Override | ||
public RuleID getID() { return RuleID.CONDENSE; } | ||
|
||
@Override | ||
public RuleGroupID getGroupID() { return RuleGroupID.COMMANDS; } | ||
|
||
@Override | ||
public String getDisplayName() { return "Replace CONDENSE with string function"; } | ||
|
||
@Override | ||
public String getDescription() { return "Replaces the CONDENSE statement with the string processing function condense( )."; } | ||
|
||
@Override | ||
public LocalDate getDateCreated() { return LocalDate.of(2024, 3, 24); } | ||
|
||
@Override | ||
public RuleReference[] getReferences() { return references; } | ||
|
||
@Override | ||
public RuleID[] getDependentRules() { return new RuleID[] { RuleID.UPPER_AND_LOWER_CASE, RuleID.ALIGN_ASSIGNMENTS, RuleID.ALIGN_PARAMETERS } ; } | ||
|
||
// getRequiredAbapRelease() not required, as these built-in functions were introduced with ABAP release 7.02 (= 7.0, EhP2) | ||
|
||
@Override | ||
public boolean isEssential() { return true; } | ||
|
||
@Override | ||
public String getExample() { | ||
return "" | ||
+ LINE_SEP + " METHOD replace_condense." | ||
+ LINE_SEP + " CONSTANTS lc_abc_with_gaps TYPE string VALUE ` a b c `." | ||
+ LINE_SEP + "" | ||
+ LINE_SEP + " DATA lv_text_a TYPE char30 VALUE lc_abc_with_gaps." | ||
+ LINE_SEP + " DATA lv_text_b TYPE char30 VALUE lc_abc_with_gaps." | ||
+ LINE_SEP + " DATA lv_text_c TYPE char30 VALUE lc_abc_with_gaps." | ||
+ LINE_SEP + " DATA lv_string_a TYPE string VALUE lc_abc_with_gaps." | ||
+ LINE_SEP + " DATA lv_string_b TYPE string VALUE lc_abc_with_gaps." | ||
+ LINE_SEP + "" | ||
+ LINE_SEP + " \" condense first text field to 'a b c', second one to 'abc'" | ||
+ LINE_SEP + " CONDENSE lv_text_a." | ||
+ LINE_SEP + " CONDENSE lv_text_b NO-GAPS." | ||
+ LINE_SEP + "" | ||
+ LINE_SEP + " \" condense first string to 'a b c', second one to 'abc'" | ||
+ LINE_SEP + " CONDENSE lv_string_a." | ||
+ LINE_SEP + " CONDENSE lv_string_b NO-GAPS." | ||
+ LINE_SEP + "" | ||
+ LINE_SEP + " \" condense text field with offset 5 and length 7 to ` a b c`" | ||
+ LINE_SEP + " \" (specifying offset and length in write positions is possible for text fields, but not for strings)" | ||
+ LINE_SEP + " CONDENSE lv_text_c+5(7)." | ||
+ LINE_SEP + " ENDMETHOD."; | ||
} | ||
|
||
final ConfigBoolValue configSpecifyValName = new ConfigBoolValue(this, "SpecifyValName", "Explicitly specify parameter val = ... even if no other parameters are used", false); | ||
final ConfigBoolValue configSpecifyDel = new ConfigBoolValue(this, "SpecifyDel", "Explicitly specify parameter del = ` `, except for NO-GAPS", false); | ||
final ConfigBoolValue configSpecifyFromForNoGaps = new ConfigBoolValue(this, "SpecifyFromForNoGaps", "Explicitly specify parameter from = ` ` for NO-GAPS", true); | ||
final ConfigBoolValue configKeepParamsOnOneLine = new ConfigBoolValue(this, "KeepParamsOnOneLine", "Keep parameters on one line (see rule '" + AlignParametersRule.DISPLAY_NAME + "', option '" + AlignParametersRule.OPTION_NAME_KEEP_OTHER_ONE_LINERS + "')", false); | ||
|
||
private final ConfigValue[] configValues = new ConfigValue[] { configSpecifyValName, configSpecifyDel, configSpecifyFromForNoGaps, configKeepParamsOnOneLine }; | ||
|
||
@Override | ||
public ConfigValue[] getConfigValues() { return configValues; } | ||
|
||
public CondenseRule(Profile profile) { | ||
super(profile); | ||
initializeConfiguration(); | ||
} | ||
|
||
@Override | ||
protected boolean executeOn(Code code, Command command, int releaseRestriction) throws UnexpectedSyntaxBeforeChanges, UnexpectedSyntaxAfterChanges { | ||
Token firstToken = command.getFirstToken(); | ||
if (firstToken == null) | ||
return false; | ||
|
||
// expect 'CONDENSE text [NO-GAPS].' without comments or pragmas | ||
if (!firstToken.matchesOnSiblings(false, "CONDENSE", TokenSearch.ANY_IDENTIFIER, TokenSearch.makeOptional("NO-GAPS"), ".")) { | ||
return false; | ||
} | ||
|
||
Token condenseKeyword = firstToken; | ||
Token identifier = condenseKeyword.getNextCodeSibling(); | ||
Token next = identifier.getNextCodeSibling(); | ||
Token noGapsKeyword = null; | ||
Token periodToken = null; | ||
if (next.isPeriod()) { | ||
periodToken = next; | ||
} else { | ||
noGapsKeyword = next; | ||
periodToken = noGapsKeyword.getNextCodeSibling(); | ||
} | ||
boolean noGaps = (noGapsKeyword != null); | ||
if (!identifier.isIdentifier() || !periodToken.isPeriod()) // pro forma | ||
return false; | ||
|
||
int sourceLineNum = identifier.sourceLineNum; | ||
|
||
// insert 'identifier = ' and remove CONDENSE keyword | ||
Token assignedToIdentifier = Token.createForAbap(condenseKeyword.lineBreaks, condenseKeyword.spacesLeft, identifier.getText(), sourceLineNum); | ||
condenseKeyword.insertLeftSibling(assignedToIdentifier); | ||
condenseKeyword.insertLeftSibling(Token.createForAbap(0, 1, "=", sourceLineNum)); | ||
condenseKeyword.removeFromCommand(); | ||
|
||
// remove NO-GAPS | ||
if (noGapsKeyword != null) | ||
noGapsKeyword.removeFromCommand(); | ||
|
||
// insert 'condense( ... )' around the identifier | ||
identifier.setWhitespace(); | ||
identifier.insertParenthesesUpTo(periodToken, "condense(", ")"); | ||
periodToken.setWhitespace(0, 0); | ||
|
||
boolean insertParamDel = configSpecifyDel.getValue() && !noGaps; | ||
boolean insertParamFrom = noGaps && configSpecifyFromForNoGaps.getValue(); | ||
boolean insertParamTo = noGaps; | ||
boolean keepOnOneLine = configKeepParamsOnOneLine.getValue(); | ||
|
||
int maxParamNameLength = (insertParamFrom ? "from".length() : "val".length()); | ||
|
||
// insert 'val =' before the identifier, if specified or needed | ||
if (configSpecifyValName.getValue() || insertParamDel || insertParamFrom || insertParamTo) { | ||
int spacesLeft = (keepOnOneLine ? 1 : maxParamNameLength - "val".length() + 1); | ||
identifier.insertLeftSibling(Token.createForAbap(0, 1, "val", sourceLineNum)); | ||
identifier.insertLeftSibling(Token.createForAbap(0, spacesLeft, "=", sourceLineNum)); | ||
} | ||
|
||
// insert further parameters, if specified or needed | ||
if (insertParamTo) | ||
insertParam("to", "``", identifier, maxParamNameLength, keepOnOneLine); | ||
|
||
if (insertParamFrom) | ||
insertParam("from", "` `", identifier, maxParamNameLength, keepOnOneLine); | ||
|
||
if (insertParamDel) | ||
insertParam("del", "` `", identifier, maxParamNameLength, keepOnOneLine); | ||
|
||
command.invalidateMemoryAccessType(); | ||
return true; | ||
} | ||
|
||
private void insertParam(String paramName, String actualValue, Token identifier, int maxParamNameLength, boolean keepOnOneLine) throws IntegrityBrokenException { | ||
int lineBreaks = (keepOnOneLine ? 0 : 1); | ||
int indent = (keepOnOneLine ? 1 : identifier.getParent().getEndIndexInLine() + 1); | ||
int spacesLeftOfAssignment = (keepOnOneLine ? 1 : maxParamNameLength - paramName.length() + 1); | ||
int sourceLineNum = identifier.sourceLineNum; | ||
|
||
identifier.insertRightSibling(Token.createForAbap(0, 1, actualValue, sourceLineNum)); | ||
identifier.insertRightSibling(Token.createForAbap(0, spacesLeftOfAssignment, "=", sourceLineNum)); | ||
identifier.insertRightSibling(Token.createForAbap(lineBreaks, indent, paramName, sourceLineNum)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
157 changes: 157 additions & 0 deletions
157
...com.sap.adt.abapcleaner.test/src/com/sap/adt/abapcleaner/rules/commands/CondenseTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package com.sap.adt.abapcleaner.rules.commands; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import com.sap.adt.abapcleaner.rulebase.RuleID; | ||
import com.sap.adt.abapcleaner.rulebase.RuleTestBase; | ||
|
||
public class CondenseTest extends RuleTestBase { | ||
private CondenseRule rule; | ||
|
||
CondenseTest() { | ||
super(RuleID.CONDENSE); | ||
rule = (CondenseRule)getRule(); | ||
} | ||
|
||
@BeforeEach | ||
void setUp() { | ||
// setup default test configuration (may be modified in the individual test methods) | ||
rule.configSpecifyValName.setValue(false); | ||
rule.configSpecifyDel.setValue(false); | ||
rule.configSpecifyFromForNoGaps.setValue(true); | ||
rule.configKeepParamsOnOneLine.setValue(false); | ||
} | ||
|
||
@Test | ||
void testSpecifyValName() { | ||
rule.configSpecifyValName.setValue(true); | ||
|
||
buildSrc(" \" comment"); | ||
buildSrc(" CONDENSE lv_text_a."); | ||
buildSrc(" CONDENSE lv_text_b NO-GAPS."); | ||
buildSrc(" CONDENSE lv_text_c+5(7)."); | ||
|
||
buildExp(" \" comment"); | ||
buildExp(" lv_text_a = condense( val = lv_text_a )."); | ||
buildExp(" lv_text_b = condense( val = lv_text_b"); | ||
buildExp(" from = ` `"); | ||
buildExp(" to = `` )."); | ||
buildExp(" lv_text_c+5(7) = condense( val = lv_text_c+5(7) )."); | ||
|
||
putAnyMethodAroundSrcAndExp(); | ||
|
||
testRule(); | ||
} | ||
|
||
@Test | ||
void testSpecifyFromForNoGaps() { | ||
buildSrc(" CONDENSE lv_text_a."); | ||
buildSrc(" CONDENSE lv_text_b NO-GAPS."); | ||
buildSrc(" CONDENSE lv_text_c+5(7)."); | ||
|
||
buildExp(" lv_text_a = condense( lv_text_a )."); | ||
buildExp(" lv_text_b = condense( val = lv_text_b"); | ||
buildExp(" from = ` `"); | ||
buildExp(" to = `` )."); | ||
buildExp(" lv_text_c+5(7) = condense( lv_text_c+5(7) )."); | ||
|
||
putAnyMethodAroundSrcAndExp(); | ||
|
||
testRule(); | ||
} | ||
|
||
@Test | ||
void testDoNotSpecifyFromForNoGaps() { | ||
rule.configSpecifyFromForNoGaps.setValue(false); | ||
|
||
buildSrc(" CONDENSE lv_text_a."); | ||
buildSrc(" CONDENSE lv_text_b NO-GAPS."); | ||
buildSrc(" CONDENSE lv_text_c+5(7)."); | ||
|
||
buildExp(" lv_text_a = condense( lv_text_a )."); | ||
buildExp(" lv_text_b = condense( val = lv_text_b"); | ||
buildExp(" to = `` )."); | ||
buildExp(" lv_text_c+5(7) = condense( lv_text_c+5(7) )."); | ||
|
||
putAnyMethodAroundSrcAndExp(); | ||
|
||
testRule(); | ||
} | ||
|
||
@Test | ||
void testSpecifyDel() { | ||
rule.configSpecifyDel.setValue(true); | ||
|
||
buildSrc(" CONDENSE lv_text_a."); | ||
buildSrc(" CONDENSE lv_text_b NO-GAPS."); | ||
buildSrc(" CONDENSE lv_text_c+5(7)."); | ||
|
||
buildExp(" lv_text_a = condense( val = lv_text_a"); | ||
buildExp(" del = ` ` )."); | ||
buildExp(" lv_text_b = condense( val = lv_text_b"); | ||
buildExp(" from = ` `"); | ||
buildExp(" to = `` )."); | ||
buildExp(" lv_text_c+5(7) = condense( val = lv_text_c+5(7)"); | ||
buildExp(" del = ` ` )."); | ||
|
||
putAnyMethodAroundSrcAndExp(); | ||
|
||
testRule(); | ||
} | ||
|
||
@Test | ||
void testSpecifyDelButNotFrom() { | ||
rule.configSpecifyDel.setValue(true); | ||
rule.configSpecifyFromForNoGaps.setValue(false); | ||
|
||
buildSrc(" CONDENSE lv_text_a."); | ||
buildSrc(" CONDENSE lv_text_b NO-GAPS."); | ||
buildSrc(" CONDENSE lv_text_c+5(7)."); | ||
|
||
buildExp(" lv_text_a = condense( val = lv_text_a"); | ||
buildExp(" del = ` ` )."); | ||
buildExp(" lv_text_b = condense( val = lv_text_b"); | ||
buildExp(" to = `` )."); | ||
buildExp(" lv_text_c+5(7) = condense( val = lv_text_c+5(7)"); | ||
buildExp(" del = ` ` )."); | ||
|
||
putAnyMethodAroundSrcAndExp(); | ||
|
||
testRule(); | ||
} | ||
|
||
@Test | ||
void testCommentsAndPragmasUnchanged() { | ||
buildSrc(" CONDENSE \" comment"); | ||
buildSrc(" lv_text_a."); | ||
buildSrc(" CONDENSE lv_text_b NO-GAPS ##ANY_PRAGMA."); | ||
buildSrc(" CONDENSE"); | ||
buildSrc("* comment line"); | ||
buildSrc(" lv_text_c+5(7)."); | ||
|
||
copyExpFromSrc(); | ||
|
||
putAnyMethodAroundSrcAndExp(); | ||
|
||
testRule(); | ||
} | ||
|
||
@Test | ||
void testKeepParamsOnOneLine() { | ||
rule.configSpecifyDel.setValue(true); | ||
rule.configKeepParamsOnOneLine.setValue(true); | ||
|
||
buildSrc(" CONDENSE lv_text_a."); | ||
buildSrc(" CONDENSE lv_text_b NO-GAPS."); | ||
buildSrc(" CONDENSE lv_text_c+5(7)."); | ||
|
||
buildExp(" lv_text_a = condense( val = lv_text_a del = ` ` )."); | ||
buildExp(" lv_text_b = condense( val = lv_text_b from = ` ` to = `` )."); | ||
buildExp(" lv_text_c+5(7) = condense( val = lv_text_c+5(7) del = ` ` )."); | ||
|
||
putAnyMethodAroundSrcAndExp(); | ||
|
||
testRule(); | ||
} | ||
} |