Skip to content

Commit 4a1a518

Browse files
committed
enhance ComparisonOperatorRule with option for preferred set (#273)
1 parent fc792aa commit 4a1a518

File tree

7 files changed

+221
-29
lines changed

7 files changed

+221
-29
lines changed

com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/base/ABAP.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,20 @@ public static String getTextualComparisonOperator(String text) {
658658
}
659659
}
660660

661-
public static boolean isComparisonOperator(String text) {
661+
public static String getNonObsoleteComparisonOperator(String text) {
662+
switch (text) {
663+
case "=<":
664+
return "<=";
665+
case "=>":
666+
return ">=";
667+
case "><":
668+
return "<>";
669+
default:
670+
return text;
671+
}
672+
}
673+
674+
public static boolean isComparisonOperator(String text, boolean includeObsoleteVariants) {
662675
switch (AbapCult.toUpper(text)) {
663676
// Comparison operators for all data types
664677
case "<":
@@ -699,6 +712,11 @@ public static boolean isComparisonOperator(String text) {
699712
case "BYTE-NS":
700713
return true;
701714

715+
case "=<":
716+
case "=>":
717+
case "><":
718+
return includeObsoleteVariants;
719+
702720
// TODO: Comparison operators for bit patterns (O, Z, M) are missing,
703721
// see https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abenlogexp_op.htm
704722

com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/parser/Command.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -3235,7 +3235,7 @@ public final boolean matchesPattern() {
32353235
// return changesSyField(ABAP.SyField.SUBRC) && SyFieldAnalyzer.getSyFieldReadersFor(ABAP.SyField.SUBRC, this).size() >= 2;
32363236
// - getCommandsRelatedToPatternMatch() can then return SyFieldAnalyzer.getSyFieldReadersFor(ABAP.SyField.SUBRC, this);
32373237

3238-
return false;
3238+
return false;
32393239
}
32403240

32413241
public final ArrayList<Command> getCommandsRelatedToPatternMatch() {

com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/parser/Token.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ public final boolean isStringTemplate() {
250250
/**
251251
* Returns true if the Token text equals the supplied comparison text.
252252
* To check the Token type at the same time, use {@link #isKeyword(String)} or {@link #isComparisonOperator(String)}
253-
* (see list of tokens classified as comparison operators in {@link ABAP#isComparisonOperator(String)})
253+
* (see list of tokens classified as comparison operators in {@link ABAP#isComparisonOperator(String, boolean)})
254254
*/
255255
public final boolean textEquals(String compareText) {
256256
return AbapCult.stringEquals(text, compareText, true);
@@ -371,7 +371,7 @@ private static TokenType inferTypeFromAbapToken(String text) {
371371
// "=" will later be differentiated between assignment and comparison operator in Command.finishBuild() / .distinguishOperators()
372372
return TokenType.ASSIGNMENT_OP;
373373

374-
} else if (ABAP.isComparisonOperator(text)) {
374+
} else if (ABAP.isComparisonOperator(text, true)) {
375375
return TokenType.COMPARISON_OP;
376376

377377
} else if (ABAP.isAbapUpperCaseKeyword(text)) {
@@ -1260,7 +1260,7 @@ public final void ensureWhitespace() {
12601260
/**
12611261
* Returns true if the Token text equals any of the supplied comparison texts.
12621262
* To check the Token type at the same time, use {@link #isAnyKeyword(String...)} or {@link #isAnyComparisonOperator(String...)}
1263-
* (see list of tokens classified as comparison operators in {@link ABAP#isComparisonOperator(String)})
1263+
* (see list of tokens classified as comparison operators in {@link ABAP#isComparisonOperator(String, boolean)})
12641264
*/
12651265
public final boolean textEqualsAny(String... compareTexts) {
12661266
for (String compareText : compareTexts) {
@@ -1889,7 +1889,7 @@ else if (token.matchesOnSiblings(true, "INSTANCE", "OF")) {
18891889
throw new UnexpectedSyntaxException(token, "predicate expression expected");
18901890
}
18911891

1892-
} else if (ABAP.isComparisonOperator(token.getText())) { // includes BETWEEN and IN
1892+
} else if (ABAP.isComparisonOperator(token.getText(), true)) { // includes BETWEEN and IN
18931893
// Comparison Expressions (rel_exp), see https://ldcier1.wdf.sap.corp:44300/sap/public/bc/abap/docu?object=abenlogexp_comp
18941894
boolean isTernaryOp = token.isComparisonOperator("BETWEEN");
18951895
Term term2 = Term.createArithmetic(token.getNextCodeSibling());

com.sap.adt.abapcleaner/src/com/sap/adt/abapcleaner/rules/syntax/ComparisonOperatorRule.java

+47-10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public class ComparisonOperatorRule extends RuleForTokens {
1212
new RuleReference(RuleSource.ABAP_CLEANER) };
1313

1414
private final static String[] textualComparisonOps = new String[] { "LT", "LE", "EQ", "NE", "GE", "GT" };
15+
private final static String[] symbolicComparisonOps = new String[] { "<", "<=", "=", "<>", ">=", ">" };
16+
private final static String[] obsoleteComparisonOps = new String[] { "><", "=<", "=>" };
1517

1618
@Override
1719
public RuleID getID() { return RuleID.COMPARISON_OPERATOR; }
@@ -20,10 +22,10 @@ public class ComparisonOperatorRule extends RuleForTokens {
2022
public RuleGroupID getGroupID() { return RuleGroupID.SYNTAX; }
2123

2224
@Override
23-
public String getDisplayName() { return "Prefer =, <>, <= etc. to EQ, NE, LE etc."; }
25+
public String getDisplayName() { return "Use consistent set of comparison operators"; }
2426

2527
@Override
26-
public String getDescription() { return "Replaces keywords (LT, LE, EQ, NE, GT, GE) with symbolic comparison operators (<, <=, =, >=, >, <>)."; }
28+
public String getDescription() { return "Replaces textual comparison operators (LT, LE, EQ, NE, GE, GT) with symbolic comparison operators (<, <=, =, <>, >=, >) or vice versa."; }
2729

2830
@Override
2931
public LocalDate getDateCreated() { return LocalDate.of(2021, 1, 17); }
@@ -32,23 +34,41 @@ public class ComparisonOperatorRule extends RuleForTokens {
3234
public RuleReference[] getReferences() { return references; }
3335

3436
@Override
35-
public RuleID[] getDependentRules() { return new RuleID[] { RuleID.ALIGN_LOGICAL_EXPRESSIONS } ; }
37+
public RuleID[] getDependentRules() { return new RuleID[] { RuleID.UPPER_AND_LOWER_CASE, RuleID.ALIGN_LOGICAL_EXPRESSIONS } ; }
3638

3739
@Override
3840
public String getExample() {
3941
return ""
40-
+ LINE_SEP + " METHOD prefer_symbolic_comparison_ops."
42+
+ LINE_SEP + "CLASS any_class IMPLEMENTATION."
43+
+ LINE_SEP + " METHOD use_consistent_comparison_ops."
4144
+ LINE_SEP + " IF a EQ b OR c NE d."
42-
+ LINE_SEP + " IF a LT c AND b GT d"
43-
+ LINE_SEP + " AND b LT e."
45+
+ LINE_SEP + " IF a < c AND b > d"
46+
+ LINE_SEP + " AND b < e."
4447
+ LINE_SEP + " IF a LE d AND c GE b."
45-
+ LINE_SEP + " \" do something"
48+
+ LINE_SEP + " result = xsdbool( a <= d OR a GE b )."
4649
+ LINE_SEP + " ENDIF."
4750
+ LINE_SEP + " ENDIF."
4851
+ LINE_SEP + " ENDIF."
49-
+ LINE_SEP + " ENDMETHOD.";
52+
+ LINE_SEP + " ENDMETHOD."
53+
+ LINE_SEP + "ENDCLASS."
54+
+ LINE_SEP
55+
+ LINE_SEP + "FORM any_form."
56+
+ LINE_SEP + " \" these obsolete variants are only possible outside of the object-oriented context:"
57+
+ LINE_SEP + " IF a >< b AND b => c OR c =< d."
58+
+ LINE_SEP + " RETURN."
59+
+ LINE_SEP + " ENDIF."
60+
+ LINE_SEP + "ENDFORM.";
5061
}
5162

63+
final ConfigEnumValue<ComparisonOperatorType> configPreferredOperatorSet = new ConfigEnumValue<ComparisonOperatorType>(this, "PreferredOperatorSet", "Preferred set of comparison operators:", new String[] { "symbolic (<, <=, =, <>, >=, >)", "textual (LT, LE, EQ, NE, GE, GT)" }, ComparisonOperatorType.values(), ComparisonOperatorType.SYMBOLIC, ComparisonOperatorType.SYMBOLIC, LocalDate.of(2024, 4, 12));
64+
final ConfigBoolValue configReplaceRegularOperators = new ConfigBoolValue(this, "ReplaceRegularOperators", "Replace regular comparison operators with preferred variant", true);
65+
final ConfigBoolValue configReplaceObsoleteOperators = new ConfigBoolValue(this, "ReplaceObsoleteOperators", "Replace obsolete comparison operators (>< => =<) with preferred variant", true, false, LocalDate.of(2024, 4, 12));
66+
67+
private final ConfigValue[] configValues = new ConfigValue[] { configPreferredOperatorSet, configReplaceRegularOperators, configReplaceObsoleteOperators };
68+
69+
@Override
70+
public ConfigValue[] getConfigValues() { return configValues; }
71+
5272
public ComparisonOperatorRule(Profile profile) {
5373
super(profile);
5474
initializeConfiguration();
@@ -62,7 +82,7 @@ protected boolean skipCommand(Command command) {
6282

6383
@Override
6484
protected boolean executeOn(Code code, Command command, Token token, int releaseRestriction) {
65-
if (!token.isComparisonOperator() || !token.isAnyComparisonOperator(textualComparisonOps))
85+
if (!token.isComparisonOperator())
6686
return false;
6787

6888
// do NOT change 'opt' in: 'SELECT-OPTIONS selcrit FOR dobj DEFAULT val1 [TO val2] [OPTION opt] ....'
@@ -73,7 +93,24 @@ protected boolean executeOn(Code code, Command command, Token token, int release
7393
}
7494
}
7595

76-
String newOperator = ABAP.getSymbolicComparisonOperator(token.getText());
96+
ComparisonOperatorType targetType = ComparisonOperatorType.forValue(configPreferredOperatorSet.getValue());
97+
String newOperator;
98+
if (configReplaceObsoleteOperators.getValue() && token.isAnyComparisonOperator(obsoleteComparisonOps) && !command.isInOOContext()) {
99+
newOperator = ABAP.getNonObsoleteComparisonOperator(token.getText());
100+
if (targetType == ComparisonOperatorType.TEXTUAL) {
101+
newOperator = ABAP.getTextualComparisonOperator(newOperator);
102+
}
103+
104+
} else if (targetType == ComparisonOperatorType.SYMBOLIC && configReplaceRegularOperators.getValue() && token.isAnyComparisonOperator(textualComparisonOps)) {
105+
newOperator = ABAP.getSymbolicComparisonOperator(token.getText());
106+
107+
} else if (targetType == ComparisonOperatorType.TEXTUAL && configReplaceRegularOperators.getValue() && token.isAnyComparisonOperator(symbolicComparisonOps)) {
108+
newOperator = ABAP.getTextualComparisonOperator(token.getText());
109+
110+
} else {
111+
return false;
112+
}
113+
77114
token.setText(newOperator, true);
78115
return true;
79116
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.sap.adt.abapcleaner.rules.syntax;
2+
3+
public enum ComparisonOperatorType {
4+
SYMBOLIC,
5+
TEXTUAL;
6+
7+
public int getValue() { return this.ordinal(); }
8+
9+
public static ComparisonOperatorType forValue(int value) {
10+
return values()[value];
11+
}
12+
}

test/com.sap.adt.abapcleaner.test/src/com/sap/adt/abapcleaner/base/AbapTest.java

+17-6
Original file line numberDiff line numberDiff line change
@@ -415,16 +415,27 @@ void testReadTillEndOfTypeName() {
415415

416416
@Test
417417
void testIsComparisonOperator() {
418-
assertTrue(ABAP.isComparisonOperator("lt"));
419-
assertTrue(ABAP.isComparisonOperator("LT"));
418+
assertTrue(ABAP.isComparisonOperator("lt", false));
419+
assertTrue(ABAP.isComparisonOperator("LT", false));
420420

421-
assertTrue(ABAP.isComparisonOperator("ca"));
422-
assertTrue(ABAP.isComparisonOperator("CA"));
421+
assertTrue(ABAP.isComparisonOperator("ca", false));
422+
assertTrue(ABAP.isComparisonOperator("CA", false));
423423

424-
assertTrue(ABAP.isComparisonOperator("byte-CA"));
425-
assertTrue(ABAP.isComparisonOperator("BYTE-CA"));
424+
assertTrue(ABAP.isComparisonOperator("byte-CA", false));
425+
assertTrue(ABAP.isComparisonOperator("BYTE-CA", false));
426426
}
427427

428+
@Test
429+
void testIsComparisonOperatorForObsoleteVariants() {
430+
assertFalse(ABAP.isComparisonOperator("><", false));
431+
assertFalse(ABAP.isComparisonOperator("=>", false));
432+
assertFalse(ABAP.isComparisonOperator("=<", false));
433+
434+
assertTrue(ABAP.isComparisonOperator("><", true));
435+
assertTrue(ABAP.isComparisonOperator("=>", true));
436+
assertTrue(ABAP.isComparisonOperator("=<", true));
437+
}
438+
428439
@Test
429440
void testParameterNull() {
430441
// cover null-case branches that are never covered otherwise in tests

test/com.sap.adt.abapcleaner.test/src/com/sap/adt/abapcleaner/rules/syntax/ComparisonOperatorTest.java

+121-7
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,81 @@
11
package com.sap.adt.abapcleaner.rules.syntax;
22

3+
import org.junit.jupiter.api.BeforeEach;
34
import org.junit.jupiter.api.Test;
45

56
import com.sap.adt.abapcleaner.rulebase.RuleID;
67
import com.sap.adt.abapcleaner.rulebase.RuleTestBase;
78

89
class ComparisonOperatorTest extends RuleTestBase {
10+
private ComparisonOperatorRule rule;
11+
912
ComparisonOperatorTest() {
1013
super(RuleID.COMPARISON_OPERATOR);
14+
rule = (ComparisonOperatorRule)getRule();
15+
}
16+
17+
@BeforeEach
18+
void setUp() {
19+
// setup default test configuration (may be modified in the individual test methods)
20+
rule.configPreferredOperatorSet.setEnumValue(ComparisonOperatorType.SYMBOLIC);
21+
rule.configReplaceRegularOperators.setValue(true);
22+
rule.configReplaceObsoleteOperators.setValue(true);
1123
}
1224

1325
@Test
14-
void testSimpleCases() {
26+
void testPreferSymbolicSimpleCases() {
1527
buildSrc(" IF a EQ b OR c NE d.");
16-
buildSrc(" IF a LT c AND b GT d.");
28+
buildSrc(" IF a < c AND b > d");
29+
buildSrc(" AND b < e.");
1730
buildSrc(" IF a LE d AND c GE b.");
18-
buildSrc(" \" do something");
31+
buildSrc(" result = xsdbool( a <= d OR a GE b ).");
1932
buildSrc(" ENDIF.");
2033
buildSrc(" ENDIF.");
2134
buildSrc(" ENDIF.");
2235

2336
buildExp(" IF a = b OR c <> d.");
24-
buildExp(" IF a < c AND b > d.");
37+
buildExp(" IF a < c AND b > d");
38+
buildExp(" AND b < e.");
2539
buildExp(" IF a <= d AND c >= b.");
26-
buildExp(" \" do something");
40+
buildExp(" result = xsdbool( a <= d OR a >= b ).");
2741
buildExp(" ENDIF.");
2842
buildExp(" ENDIF.");
2943
buildExp(" ENDIF.");
3044

3145
putAnyMethodAroundSrcAndExp();
32-
46+
47+
testRule();
48+
}
49+
50+
@Test
51+
void testPreferTextualSimpleCases() {
52+
rule.configPreferredOperatorSet.setEnumValue(ComparisonOperatorType.TEXTUAL);
53+
54+
buildSrc(" IF a EQ b OR c NE d.");
55+
buildSrc(" IF a < c AND b > d");
56+
buildSrc(" AND b < e.");
57+
buildSrc(" IF a LE d AND c GE b.");
58+
buildSrc(" result = xsdbool( a <= d OR a GE b ).");
59+
buildSrc(" ENDIF.");
60+
buildSrc(" ENDIF.");
61+
buildSrc(" ENDIF.");
62+
63+
buildExp(" IF a EQ b OR c NE d.");
64+
buildExp(" IF a LT c AND b GT d");
65+
buildExp(" AND b LT e.");
66+
buildExp(" IF a LE d AND c GE b.");
67+
buildExp(" result = xsdbool( a LE d OR a GE b ).");
68+
buildExp(" ENDIF.");
69+
buildExp(" ENDIF.");
70+
buildExp(" ENDIF.");
71+
72+
putAnyMethodAroundSrcAndExp();
73+
3374
testRule();
3475
}
3576

3677
@Test
37-
void testMultiLine() {
78+
void testPreferSymbolicMultiLine() {
3879
// ensure that the indent of the second line is adjusted to the shortened = operator
3980

4081
buildSrc(" IF a EQ b OR a LT 0");
@@ -64,4 +105,77 @@ void testSelectOptionsOptionKept() {
64105

65106
testRule();
66107
}
108+
109+
110+
@Test
111+
void testObsoleteToSymbolic() {
112+
buildSrc("FORM any_form.");
113+
buildSrc(" IF a >< b AND b => c OR c =< d.");
114+
buildSrc(" RETURN.");
115+
buildSrc(" ENDIF.");
116+
buildSrc("ENDFORM.");
117+
118+
buildExp("FORM any_form.");
119+
buildExp(" IF a <> b AND b >= c OR c <= d.");
120+
buildExp(" RETURN.");
121+
buildExp(" ENDIF.");
122+
buildExp("ENDFORM.");
123+
124+
testRule();
125+
}
126+
127+
128+
@Test
129+
void testObsoleteToTextual() {
130+
rule.configPreferredOperatorSet.setEnumValue(ComparisonOperatorType.TEXTUAL);
131+
132+
buildSrc("FORM any_form.");
133+
buildSrc(" IF a >< b AND b => c OR c =< d.");
134+
buildSrc(" RETURN.");
135+
buildSrc(" ENDIF.");
136+
buildSrc("ENDFORM.");
137+
138+
buildExp("FORM any_form.");
139+
buildExp(" IF a NE b AND b GE c OR c LE d.");
140+
buildExp(" RETURN.");
141+
buildExp(" ENDIF.");
142+
buildExp("ENDFORM.");
143+
144+
testRule();
145+
}
146+
147+
@Test
148+
void testKeepRegularOperators() {
149+
rule.configReplaceRegularOperators.setValue(false);
150+
151+
buildSrc(" IF a EQ b OR c NE d.");
152+
buildSrc(" IF a < c AND b > d");
153+
buildSrc(" AND b < e.");
154+
buildSrc(" IF a LE d AND c GE b.");
155+
buildSrc(" result = xsdbool( a <= d OR a GE b ).");
156+
buildSrc(" ENDIF.");
157+
buildSrc(" ENDIF.");
158+
buildSrc(" ENDIF.");
159+
160+
copyExpFromSrc();
161+
162+
putAnyMethodAroundSrcAndExp();
163+
164+
testRule();
165+
}
166+
167+
@Test
168+
void testKeepObsoleteOperators() {
169+
rule.configReplaceObsoleteOperators.setValue(false);
170+
171+
buildSrc("FORM any_form.");
172+
buildSrc(" IF a >< b AND b => c OR c =< d.");
173+
buildSrc(" RETURN.");
174+
buildSrc(" ENDIF.");
175+
buildSrc("ENDFORM.");
176+
177+
copyExpFromSrc();
178+
179+
testRule();
180+
}
67181
}

0 commit comments

Comments
 (0)