-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathCallMethodRule.java
160 lines (134 loc) · 7.38 KB
/
CallMethodRule.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
155
156
157
158
159
160
package com.sap.adt.abapcleaner.rules.commands;
import java.time.LocalDate;
import java.util.ArrayList;
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.rules.syntax.ExportingKeywordRule;
import com.sap.adt.abapcleaner.rules.syntax.ReceivingKeywordRule;
/**
* Replaces CALL METHOD with a functional call, adding parentheses if needed.
* (parentheses may already be in the code: "CALL METHOD cx_message=>raise_symsg( )" );
* excludes cases of dynamic typing which cannot be replaced: CALL METHOD modify->(method_name) EXPORTING ...
*
*/
public class CallMethodRule extends RuleForCommands {
private final static RuleReference[] references = new RuleReference[] {
new RuleReference(RuleSource.ABAP_STYLE_GUIDE, "Prefer functional to procedural calls", "#prefer-functional-to-procedural-calls"),
new RuleReference(RuleSource.ABAP_STYLE_GUIDE, "Avoid obsolete language elements", "#avoid-obsolete-language-elements"),
new RuleReference(RuleSource.CODE_PAL_FOR_ABAP, "CALL METHOD Usage", "call-method-usage.md"),
new RuleReference(RuleSource.ABAP_KEYWORD_DOCU, "Formulate static method calls without CALL METHOD", "abenmethod_call_guidl.htm"),
new RuleReference(RuleSource.ABAP_KEYWORD_DOCU, "Obsolete Calls: CALL METHOD, Static", "abapcall_method_static.htm") };
@Override
public RuleID getID() { return RuleID.CALL_METHOD; }
@Override
public RuleGroupID getGroupID() { return RuleGroupID.COMMANDS; }
@Override
public String getDisplayName() { return "Replace CALL METHOD with functional call"; }
@Override
public String getDescription() { return "Replaces obsolete CALL METHOD statements with functional calls, adding parentheses, if missing."; }
@Override
public String getHintsAndRestrictions() { return "Keeps CALL METHOD if dynamic typing is used for the method name, since no function equivalent is available here."; }
@Override
public LocalDate getDateCreated() { return LocalDate.of(2020, 12, 28); }
@Override
public RuleReference[] getReferences() { return references; }
@Override
public RuleID[] getDependentRules() { return new RuleID[] { RuleID.UPPER_AND_LOWER_CASE, RuleID.ALIGN_PARAMETERS } ; }
@Override
public boolean isEssential() { return true; }
@Override
public String getExample() {
return ""
+ LINE_SEP + " METHOD replace_call_method."
+ LINE_SEP + " CALL METHOD lo_any_instance->any_method"
+ LINE_SEP + " EXPORTING iv_param = iv_param"
+ LINE_SEP + " IMPORTING ev_param = ev_param"
+ LINE_SEP + " CHANGING cv_param = cv_param."
+ LINE_SEP
+ LINE_SEP + " \" obsolete EXPORTING will be removed:"
+ LINE_SEP + " CALL METHOD any_method_name"
+ LINE_SEP + " EXPORTING iv_name = iv_name"
+ LINE_SEP + " iv_value = iv_value."
+ LINE_SEP
+ LINE_SEP + " \" RECEIVING may be omitted, depending on Omit RECEIVING rule:"
+ LINE_SEP + " CALL METHOD other_method_name"
+ LINE_SEP + " EXPORTING iv_name = iv_name"
+ LINE_SEP + " iv_value = iv_value"
+ LINE_SEP + " RECEIVING rv_result = DATA(lv_result)."
+ LINE_SEP
+ LINE_SEP + " \" dynamic method calls cannot be replaced"
+ LINE_SEP + " CALL METHOD mo_any_instance->(iv_dynamic_method_name)"
+ LINE_SEP + " EXPORTING iv_par = iv_par. "
+ LINE_SEP
+ LINE_SEP + " CALL METHOD (iv_dynamic_class_name)=>(iv_dynamic_method_name)"
+ LINE_SEP + " EXPORTING iv_par = iv_par. "
+ LINE_SEP
+ LINE_SEP + " \" chains can only be processed if they are first unchained"
+ LINE_SEP + " CALL METHOD: any_method,"
+ LINE_SEP + " other_method EXPORTING iv_value = iv_value,"
+ LINE_SEP + " mo_any_instance->(iv_dynamic_method_name)."
+ LINE_SEP
+ LINE_SEP + " CALL METHOD third_method EXPORTING iv_name =: 'abc', 'def', 'ghi'."
+ LINE_SEP + " ENDMETHOD.";
}
final ConfigBoolValue configProcessChains = new ConfigBoolValue(this, "ProcessChains", "Unchain CALL METHOD: chains (required for processing them with this rule)", true, false, LocalDate.of(2023, 10, 27));
private final ConfigValue[] configValues = new ConfigValue[] { configProcessChains };
@Override
public ConfigValue[] getConfigValues() { return configValues; }
public CallMethodRule(Profile profile) {
super(profile);
initializeConfiguration();
}
@Override
protected boolean executeOn(Code code, Command command, int releaseRestriction) throws UnexpectedSyntaxAfterChanges {
Token firstToken = command.getFirstToken();
if (!firstToken.matchesOnSiblings(false, "CALL", "METHOD", TokenSearch.makeOptional(":"), TokenSearch.ANY_IDENTIFIER))
return false;
ArrayList<Command> unchainedCommands = unchain(code, command, configProcessChains.getValue());
if (unchainedCommands == null || unchainedCommands.isEmpty())
return false;
for (Command unchainedCommand : unchainedCommands) {
if (executeOnUnchained(code, unchainedCommand, releaseRestriction)) {
code.addRuleUse(this, unchainedCommand);
}
}
return false; // addRuleUse() was already called above
}
private boolean executeOnUnchained(Code code, Command command, int releaseRestriction) throws UnexpectedSyntaxAfterChanges {
Token firstToken = command.getFirstToken();
if (!firstToken.matchesOnSiblings(false, "CALL", "METHOD", TokenSearch.ANY_IDENTIFIER))
return false;
// the replacement is NOT possible for dynamic typing: CALL METHOD modify->(method_name) EXPORTING ...
Token methodName = firstToken.getNext().getNext();
if (methodName.textEndsWith("(") && methodName.getNext() != null && !methodName.getNext().isPrecededByWhitespace())
return false;
command.getFirstToken().removeFromCommand(); // CALL
command.getFirstToken().removeFromCommand(); // METHOD
if (methodName.textEndsWith("(")) {
// move to the end of the call chain
while (methodName.getNextSibling().getOpensLevel()) {
methodName = methodName.getNextSibling();
}
} else {
// in this case, there can be no call chain, so put everything that follows methodName in parentheses
methodName.appendParenthesesUpTo(command.getLastNonCommentToken(), true);
}
// if activated, remove the RECEIVING keyword by executing the ReceivingKeywordRule
ReceivingKeywordRule receivingKeywordRule = (ReceivingKeywordRule)parentProfile.getRule(RuleID.RECEIVING_KEYWORD);
if (receivingKeywordRule.isActive) {
Token receivingKeyword = command.getFirstToken().getLastTokenDeep(true, TokenSearch.ASTERISK, "RECEIVING");
if (receivingKeyword != null && receivingKeyword.isKeyword()) {
try {
receivingKeywordRule.executeOn(code, command, receivingKeyword, releaseRestriction);
} catch (UnexpectedSyntaxBeforeChanges e) {
throw new UnexpectedSyntaxAfterChanges(this, command, "error removing RECEIVING keyword");
}
}
}
// remove optional EXPORTING by executing ExportingKeywordRule, regardless of whether the Rule is blocked for this Command
((ExportingKeywordRule)parentProfile.getRule(RuleID.EXPORTING_KEYWORD)).executeOn(code, command, methodName, false, releaseRestriction);
return true;
}
}