-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathSelfReferenceMeRule.java
153 lines (127 loc) · 6.8 KB
/
SelfReferenceMeRule.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
package com.sap.adt.abapcleaner.rules.syntax;
import java.time.LocalDate;
import com.sap.adt.abapcleaner.base.AbapCult;
import com.sap.adt.abapcleaner.parser.*;
import com.sap.adt.abapcleaner.programbase.Program;
import com.sap.adt.abapcleaner.programbase.UnexpectedSyntaxAfterChanges;
import com.sap.adt.abapcleaner.rulebase.*;
import com.sap.adt.abapcleaner.rulehelpers.Variables;
import com.sap.adt.abapcleaner.rulehelpers.MethodInfo;
public class SelfReferenceMeRule extends RuleForDeclarations {
private final static RuleReference[] references = new RuleReference[] {
new RuleReference(RuleSource.ABAP_STYLE_GUIDE, "Omit the self-reference me when calling an instance attribute or method", "#omit-the-self-reference-me-when-calling-an-instance-attribute-or-method"),
new RuleReference(RuleSource.CODE_PAL_FOR_ABAP, "Self-Reference", "self-reference.md") };
private final String selfReferenceMe = "me->";
@Override
public RuleID getID() { return RuleID.SELF_REFERENCE_ME; }
@Override
public RuleGroupID getGroupID() { return RuleGroupID.SYNTAX; }
@Override
public String getDisplayName() { return "Remove the self-reference me->"; }
@Override
public String getDescription() { return "Removes the self-reference me->."; }
@Override
public String getHintsAndRestrictions() { return "Note that for attributes, me-> cannot be removed if " + Program.PRODUCT_NAME + " cannot 'see' the method signature (e.g. for inherited or interface methods), because a parameter could have the same name."; }
@Override
public LocalDate getDateCreated() { return LocalDate.of(2021, 1, 7); }
@Override
public RuleReference[] getReferences() { return references; }
@Override
public boolean isEssential() { return true; }
@Override
public String getExample() {
return ""
+ LINE_SEP + "CLASS any_class DEFINITION."
+ LINE_SEP + " PUBLIC SECTION."
+ LINE_SEP + " METHODS remove_self_reference_me"
+ LINE_SEP + " EXPORTING ev_result TYPE x"
+ LINE_SEP + " ev_count TYPE y"
+ LINE_SEP + " ets_result TYPE z."
+ LINE_SEP + " METHODS signature_out_of_sight REDEFINITION."
+ LINE_SEP + " PRIVATE SECTION."
+ LINE_SEP + " \" DATA ..."
+ LINE_SEP + "ENDCLASS."
+ LINE_SEP + ""
+ LINE_SEP + "CLASS any_class IMPLEMENTATION."
+ LINE_SEP + " METHOD remove_self_reference_me."
+ LINE_SEP + " me->any_method( a = 5"
+ LINE_SEP + " b = 'abc' )."
+ LINE_SEP + ""
+ LINE_SEP + " ev_result = me->mv_value."
+ LINE_SEP + ""
+ LINE_SEP + " ev_count += me->get_count( )."
+ LINE_SEP + ""
+ LINE_SEP + " ets_result = VALUE #( ( line_id = 1 price = me->mv_price_1 )"
+ LINE_SEP + " ( line_id = 2 price = me->create_price( iv_currency = me->get_currency( )"
+ LINE_SEP + " iv_amount = me->mv_amount ) ) )."
+ LINE_SEP + " ENDMETHOD."
+ LINE_SEP + ""
+ LINE_SEP + " METHOD signature_out_of_sight."
+ LINE_SEP + " \" 'amount' and 'price' could be IMPORTING parameters, so me-> must NOT be removed"
+ LINE_SEP + " me->amount = me->price + 1."
+ LINE_SEP + " ENDMETHOD."
+ LINE_SEP + "ENDCLASS.";
}
public SelfReferenceMeRule(Profile profile) {
super(profile);
initializeConfiguration();
}
@Override
protected void executeOn(Code code, Command methodStart, Variables localVariables, int releaseRestriction) throws UnexpectedSyntaxAfterChanges {
Command command = methodStart.getNext();
while (command != null && command != methodStart.getNextSibling()) {
if (!isCommandBlocked(command)) {
Token token = command.getFirstToken();
while (token != null) {
if (token.isIdentifier() && token.textStartsWith(selfReferenceMe)
&& (token.isFirstTokenInCommand() || token.getPrev().isAssignmentOperator() || token.getPrev().isComparisonOperator())) {
if (executeOn(code, command, token, localVariables)) {
code.addRuleUse(this, command);
}
}
token = token.getNext();
}
}
command = command.getNext();
}
}
private boolean executeOn(Code code, Command command, Token token, Variables localVariables) throws UnexpectedSyntaxAfterChanges {
// determine the identifier behind 'me->'; note that token may contain expressions like 'me->mo_instance->any_method('
boolean isInOOContext = command.isInOOContext();
String expression = token.getText().substring(selfReferenceMe.length());
String identifier = Variables.getObjectName(expression, isInOOContext);
boolean identifierIsMethodName = (expression.endsWith("(") && identifier.length() == expression.length() - ")".length());
boolean isMethodSignatureKnown = localVariables.isMethodSignatureKnown();
MethodInfo methodInfo = localVariables.getMethodInfo();
if (!identifierIsMethodName) {
// me-> is followed by an attribute (including cases like 'me->mo_instance->any_method(');
// if there is a local variable with the same name as that attribute, then do NOT remove 'me->'
if (localVariables.containsVariableInfo(identifier, false))
return false;
// if there is a method parameter with the same name as the attribute, do NOT remove 'me->'
// (typically, IMPORTING parameters in constructors are stored to attributes: 'me->name = name.')
if (isMethodSignatureKnown && methodInfo.hasParameter(identifier)) // pro forma - localVariables already contains the parameters
return false;
if (!isMethodSignatureKnown) {
// if the method signature is unknown (e.g. because the method is redefined from a base class whose definition
// is not visible to ABAP cleaner), check the typical case 'me->name = name'; in such a case, 'me->' must (very likely)
// NOT be removed
if (token.isFirstTokenInCommand() && token.getNext() != null && token.getNext().isAssignmentOperator()) {
Token valueToken = token.getNext().getNext();
while (valueToken != null) {
if (valueToken.isIdentifier() && AbapCult.stringEquals(identifier, Variables.getObjectName(valueToken.getText(), isInOOContext), true))
return false;
valueToken = valueToken.getNext();
}
}
// TODO: at this point, we cannot be completely sure whether there may be a parameter of the same name as the identifier;
// currently, we therefore refrain from removing 'me->'; however, this decision could be exposed to the user
// by a configuration setting (with a cautious default), since the whole problem can NOT come up if prefixes like
// mv_, iv_, ev_ etc. are used
return false;
}
}
token.setText(token.getText().substring(selfReferenceMe.length()), true);
return true;
}
}