-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathCheckOutsideLoopRule.java
154 lines (132 loc) · 7.49 KB
/
CheckOutsideLoopRule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com.sap.adt.abapcleaner.rules.commands;
import java.time.LocalDate;
import com.sap.adt.abapcleaner.base.ABAP;
import com.sap.adt.abapcleaner.parser.*;
import com.sap.adt.abapcleaner.programbase.UnexpectedSyntaxAfterChanges;
import com.sap.adt.abapcleaner.programbase.UnexpectedSyntaxBeforeChanges;
import com.sap.adt.abapcleaner.rulebase.*;
import com.sap.adt.abapcleaner.rulehelpers.NegationStyle;
public class CheckOutsideLoopRule extends CheckStatementRuleBase {
private final static RuleReference[] references = new RuleReference[] {
new RuleReference(RuleSource.ABAP_STYLE_GUIDE, "CHECK vs. RETURN", "#check-vs-return"),
new RuleReference(RuleSource.ABAP_STYLE_GUIDE, "Avoid CHECK in other positions", "#avoid-check-in-other-positions"),
new RuleReference(RuleSource.CODE_PAL_FOR_ABAP, "CHECK Statement Position", "check-statement-position.md")
};
@Override
public RuleID getID() { return RuleID.CHECK_OUTSIDE_LOOP; }
@Override
public RuleGroupID getGroupID() { return RuleGroupID.COMMANDS; }
@Override
public String getDisplayName() { return "Convert CHECK outside loop to IF NOT ... RETURN"; }
@Override
public String getDescription() { return "Converts CHECK that is found outside of loops (LOOP, DO, WHILE) to IF NOT ... RETURN."; }
@Override
public LocalDate getDateCreated() { return LocalDate.of(2021, 1, 17); }
@Override
public RuleReference[] getReferences() { return references; }
@Override
public RuleID[] getDependentRules() { return new RuleID[] { RuleID.UPPER_AND_LOWER_CASE } ; }
@Override
public boolean isEssential() { return true; }
@Override
public String getExample() {
return ""
+ LINE_SEP + " METHOD convert_check_outside_loop."
+ LINE_SEP + " \" CHECKs at earliest possible position"
+ LINE_SEP + " CHECK its_table IS NOT INITIAL."
+ LINE_SEP
+ LINE_SEP + " CHECK is_item_buffer-item_id IS NOT INITIAL"
+ LINE_SEP + " AND is_item_buffer-first_flag = abap_false"
+ LINE_SEP + " AND is_item_buffer-second_flag = abap_false"
+ LINE_SEP + " AND is_item_buffer-last_flag = abap_true."
+ LINE_SEP
+ LINE_SEP + " DATA: lv_any_value TYPE i,"
+ LINE_SEP + " lv_another_value TYPE string."
+ LINE_SEP
+ LINE_SEP + " FIELD-SYMBOLS <ls_struc> LIKE LINE OF its_table."
+ LINE_SEP
+ LINE_SEP + " \" CHECKs only preceded by declarations"
+ LINE_SEP + " CHECK ( c IS NOT SUPPLIED OR b IS INITIAL ) AND ( d IS SUPPLIED OR b IS NOT INITIAL )."
+ LINE_SEP
+ LINE_SEP + " CLEAR ev_success."
+ LINE_SEP
+ LINE_SEP + " \" CHECKs only preceded by declarations and CLEAR"
+ LINE_SEP + " CHECK a = abap_false AND b > 3 OR a = abap_true AND b <= 10."
+ LINE_SEP
+ LINE_SEP + " \" chains can only be processed if they are first unchained"
+ LINE_SEP + " CHECK: a IS NOT INITIAL,"
+ LINE_SEP + " b < 5."
+ LINE_SEP
+ LINE_SEP + " lv_value = 1."
+ LINE_SEP
+ LINE_SEP + " \" CHECKs inside the method"
+ LINE_SEP + " CHECK line_exists( its_table[ 0 ] ) "
+ LINE_SEP + " OR lines( its_table ) > 2 AND line_exists( its_table[ 1 ] )."
+ LINE_SEP + " ENDMETHOD."
+ LINE_SEP
+ LINE_SEP
+ LINE_SEP + " METHOD check_after_checkpoints."
+ LINE_SEP + " \" various checkpoints"
+ LINE_SEP + " BREAK-POINT."
+ LINE_SEP + " LOG-POINT ID any_id."
+ LINE_SEP + " ASSERT iv_value > 0."
+ LINE_SEP
+ LINE_SEP + " CHECK its_table IS NOT INITIAL."
+ LINE_SEP
+ LINE_SEP + " DATA lv_any_value TYPE i."
+ LINE_SEP
+ LINE_SEP + " CLEAR ev_success."
+ LINE_SEP + " CHECK its_table IS NOT INITIAL."
+ LINE_SEP + " ENDMETHOD.";
}
final ConfigEnumValue<KeepCheckOutsideLoopCondition> configKeepCondition = new ConfigEnumValue<KeepCheckOutsideLoopCondition>(this, "KeepCondition", "Keep CHECK statement:", new String[] { "never", "at method start", "after declarations", "after declarations and CLEAR statements" }, KeepCheckOutsideLoopCondition.values(), KeepCheckOutsideLoopCondition.KEEP_AFTER_DECLARATIONS);
final ConfigBoolValue configAllowCheckAfterCheckpoints = new ConfigBoolValue(this, "AllowCheckAfterCheckpoints", "Allow CHECK after ASSERT, BREAK-POINT and LOG-POINT", true, false, LocalDate.of(2023, 10, 9));
final ConfigBoolValue configProcessChains = new ConfigBoolValue(this, "ProcessChains", "Unchain CHECK: chains outside loops (required for processing them with this rule)", true, false, LocalDate.of(2023, 10, 27));
private final ConfigValue[] configValues = new ConfigValue[] { configKeepCondition, configNegationStyle, configConvertAbapFalseAndAbapTrue, configAllowCheckAfterCheckpoints, configProcessChains };
@Override
public ConfigValue[] getConfigValues() { return configValues; }
public CheckOutsideLoopRule(Profile profile) {
super(profile);
initializeConfiguration();
}
@Override
public void executeOn(Code code, int releaseRestriction) throws UnexpectedSyntaxBeforeChanges, UnexpectedSyntaxAfterChanges {
if (code == null)
throw new NullPointerException("code");
KeepCheckOutsideLoopCondition keepCondition = KeepCheckOutsideLoopCondition.forValue(configKeepCondition.getValue()); // the condition configured by the user
KeepCheckOutsideLoopCondition convertUpTo = KeepCheckOutsideLoopCondition.NEVER; // tells which condition(s) would still be satisfied at the position of the current Command
NegationStyle negationStyle = NegationStyle.forValue(configNegationStyle.getValue());
boolean convertAbapFalseAndAbapTrue = configConvertAbapFalseAndAbapTrue.getValue();
boolean allowCheckAfterCheckpoints = configAllowCheckAfterCheckpoints.getValue();
boolean processChains = configProcessChains.getValue();
boolean isInsideMethod = false;
Command command = code.firstCommand;
while (command != null) {
commandForErrorMsg = command;
// update "convert up to"
if (command.isMethodFunctionOrFormStart()) {
isInsideMethod = true;
convertUpTo = KeepCheckOutsideLoopCondition.NEVER;
} else if (command.isMethodFunctionOrFormEnd()) {
isInsideMethod = false;
convertUpTo = KeepCheckOutsideLoopCondition.NEVER;
} else if (command.isDeclaration() || command.isDeclarationInclude()) {
convertUpTo = KeepCheckOutsideLoopCondition.KEEP_AT_METHOD_START;
} else if (command.firstCodeTokenIsKeyword("CLEAR")) {
convertUpTo = KeepCheckOutsideLoopCondition.KEEP_AFTER_DECLARATIONS;
} else if (allowCheckAfterCheckpoints && command.firstCodeTokenIsAnyKeyword("ASSERT", "BREAK-POINT", "LOG-POINT")) {
// keep convertUpTo unchanged
} else if (!command.firstCodeTokenIsKeyword("CHECK") && !command.isCommentLine()) {
convertUpTo = KeepCheckOutsideLoopCondition.KEEP_AFTER_DECLARATIONS_AND_CLEAR;
}
// only change the statement if we are SURE to be inside a method, because if only a code snippet from a LOOP body is processed, CHECK may be erroneously converted to IF ... RETURN!
if (isInsideMethod && !isCommandBlocked(command) && command.firstCodeTokenIsKeyword("CHECK") && keepCondition.getValue() <= convertUpTo.getValue())
executeOn(code, command, false, processChains, negationStyle, convertAbapFalseAndAbapTrue, releaseRestriction);
if (command.getOpensLevel() && command.firstCodeTokenIsAnyKeyword(ABAP.loopKeywords)) {
command = command.getNextSibling();
} else {
command = command.getNext();
}
}
}
}