19
19
*/
20
20
package org .sonar .python .checks .cdk ;
21
21
22
- import java .util .ArrayList ;
23
- import java .util .Collections ;
22
+ import java .util .Collection ;
24
23
import java .util .HashMap ;
25
- import java .util .List ;
26
24
import java .util .Map ;
27
25
import java .util .Optional ;
28
26
import java .util .function .BiConsumer ;
29
- import java .util .function .Predicate ;
30
27
import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
31
28
import org .sonar .plugins .python .api .SubscriptionCheck ;
32
29
import org .sonar .plugins .python .api .SubscriptionContext ;
33
30
import org .sonar .plugins .python .api .symbols .Symbol ;
34
31
import org .sonar .plugins .python .api .tree .CallExpression ;
35
- import org .sonar .plugins .python .api .tree .Expression ;
36
- import org .sonar .plugins .python .api .tree .Name ;
37
- import org .sonar .plugins .python .api .tree .RegularArgument ;
38
- import org .sonar .plugins .python .api .tree .StringLiteral ;
39
- import org .sonar .plugins .python .api .tree .Token ;
40
32
import org .sonar .plugins .python .api .tree .Tree ;
41
- import org .sonar .python .checks .Expressions ;
42
- import org .sonar .python .tree .TreeUtils ;
43
33
34
+ /**
35
+ * Since most CDK related checks check arguments of method calls or object initializations,
36
+ * this abstract class can be used to register CallExpression consumers for various fully qualified names.
37
+ * For this purpose the method {@link #checkFqn(String, BiConsumer)} or {@link #checkFqns(Collection, BiConsumer)}
38
+ * must be called in the {@link #registerFqnConsumer()} method which has to be implemented.
39
+ */
44
40
public abstract class AbstractCdkResourceCheck extends PythonSubscriptionCheck {
45
41
46
42
private final Map <String , BiConsumer <SubscriptionContext , CallExpression >> fqnCallConsumers = new HashMap <>();
@@ -59,150 +55,22 @@ protected void visitNode(SubscriptionContext ctx) {
59
55
.ifPresent (consumer -> consumer .accept (ctx , node ));
60
56
}
61
57
62
- // TODO should be abstract when resourceFqn and visitResourceConstructor are dropped
63
- protected void registerFqnConsumer () {
64
- checkFqn (resourceFqn (), this ::visitResourceConstructor );
65
- }
66
-
67
- protected void checkFqn (String fqn , BiConsumer <SubscriptionContext , CallExpression > consumer ) {
68
- fqnCallConsumers .put (fqn , consumer );
69
- }
70
-
71
- protected String resourceFqn () {
72
- throw new UnsupportedOperationException ("Override at least resourceFqn or registerFqnConsumer" );
73
- }
74
-
75
- protected void visitResourceConstructor (SubscriptionContext ctx , CallExpression resourceConstructor ) {
76
- throw new UnsupportedOperationException ("When using resourceFqn override this method" );
77
- }
78
-
79
- protected static Optional <ArgumentTrace > getArgument (SubscriptionContext ctx , CallExpression callExpression , String argumentName ) {
80
- return callExpression .arguments ().stream ()
81
- .map (RegularArgument .class ::cast )
82
- .filter (regularArgument -> regularArgument .keywordArgument () != null )
83
- .filter (regularArgument -> argumentName .equals (regularArgument .keywordArgument ().name ()))
84
- .map (regularArgument -> ArgumentTrace .build (ctx , regularArgument .expression ()))
85
- .findAny ();
86
- }
58
+ protected abstract void registerFqnConsumer ();
87
59
88
60
/**
89
- * For compatibility with other classes and branches.
90
- * TODO Can be removed at the end of the sprint to reduce complexity.
61
+ * Register a consumer for a single FQN
91
62
*/
92
- public static class ArgumentTrace extends ExpressionTrace {
93
- private ArgumentTrace (SubscriptionContext ctx , List <Expression > trace ) {
94
- super (ctx , trace );
95
- }
96
-
97
- protected static ArgumentTrace build (SubscriptionContext ctx , Expression expression ) {
98
- List <Expression > trace = new ArrayList <>();
99
- buildTrace (expression , trace );
100
- return new ArgumentTrace (ctx , trace );
101
- }
102
- }
103
-
104
- static class ExpressionTrace {
105
-
106
- private static final String TAIL_MESSAGE = "Propagated setting." ;
107
-
108
- private final SubscriptionContext ctx ;
109
- private final List <Expression > trace ;
110
-
111
- private ExpressionTrace (SubscriptionContext ctx , List <Expression > trace ) {
112
- this .ctx = ctx ;
113
- this .trace = Collections .unmodifiableList (trace );
114
- }
115
- protected static ExpressionTrace build (SubscriptionContext ctx , Expression expression ) {
116
- List <Expression > trace = new ArrayList <>();
117
- buildTrace (expression , trace );
118
- return new ExpressionTrace (ctx , trace );
119
- }
120
-
121
- static void buildTrace (Expression expression , List <Expression > trace ) {
122
- trace .add (expression );
123
- if (expression .is (Tree .Kind .NAME )) {
124
- Expression singleAssignedValue = Expressions .singleAssignedValue (((Name ) expression ));
125
- if (singleAssignedValue != null && !trace .contains (singleAssignedValue )) {
126
- buildTrace (singleAssignedValue , trace );
127
- }
128
- }
129
- }
130
-
131
- public void addIssue (String primaryMessage ) {
132
- PreciseIssue issue = ctx .addIssue (trace .get (0 ).parent (), primaryMessage );
133
- trace .stream ().skip (1 ).forEach (expression -> issue .secondary (expression .parent (), TAIL_MESSAGE ));
134
- }
135
-
136
- public void addIssueIf (Predicate <Expression > predicate , String primaryMessage ) {
137
- if (hasExpression (predicate )) {
138
- addIssue (primaryMessage );
139
- }
140
- }
141
-
142
- public void addIssueIf (Predicate <Expression > predicate , String primaryMessage , CallExpression call ) {
143
- if (hasExpression (predicate )) {
144
- ctx .addIssue (call .callee (), primaryMessage );
145
- }
146
- }
147
-
148
- public boolean hasExpression (Predicate <Expression > predicate ) {
149
- return trace .stream ().anyMatch (predicate );
150
- }
151
-
152
- public Optional <Expression > getExpression (Predicate <Expression > predicate ) {
153
- return trace .stream ().filter (predicate ).findFirst ();
154
- }
155
-
156
- public List <Expression > trace () {
157
- return trace ;
158
- }
159
- }
160
-
161
- protected static Predicate <Expression > isFalse () {
162
- return expression -> Optional .ofNullable (expression .firstToken ()).map (Token ::value ).filter ("False" ::equals ).isPresent ();
163
- }
164
-
165
- protected static Predicate <Expression > isNone () {
166
- return expression -> expression .is (Tree .Kind .NONE );
167
- }
168
-
169
- protected static Predicate <Expression > isFqn (String fqnValue ) {
170
- return expression -> Optional .ofNullable (TreeUtils .fullyQualifiedNameFromExpression (expression ))
171
- .filter (fqnValue ::equals )
172
- .isPresent ();
173
- }
174
-
175
- protected static Optional <String > getStringValue (Expression expression ) {
176
- try {
177
- return Optional .of (((StringLiteral ) expression ).trimmedQuotesValue ());
178
- } catch (ClassCastException e ) {
179
- return Optional .empty ();
180
- }
63
+ protected void checkFqn (String fqn , BiConsumer <SubscriptionContext , CallExpression > consumer ) {
64
+ fqnCallConsumers .put (fqn , consumer );
181
65
}
182
66
183
67
/**
184
- * @return Predicate which tests if expression is a string and is equal the expected value
68
+ * Register a consumer for multiple FQNs
185
69
*/
186
- protected static Predicate < Expression > isStringValue ( String expectedValue ) {
187
- return expression -> getStringValue ( expression ). filter ( expectedValue :: equals ). isPresent ( );
70
+ protected void checkFqns ( Collection < String > suffixes , BiConsumer < SubscriptionContext , CallExpression > consumer ) {
71
+ suffixes . forEach ( suffix -> checkFqn ( suffix , consumer ) );
188
72
}
189
73
190
- protected static Predicate <Expression > isSensitiveMethod (SubscriptionContext ctx , String methodFqn , String argName , Predicate <Expression > sensitiveValuePredicate ) {
191
- return expression -> {
192
- if (!isFqn (methodFqn ).test (expression )) {
193
- return false ;
194
- }
195
- if (!expression .is (Tree .Kind .CALL_EXPR )) {
196
- return true ;
197
- }
198
74
199
- Optional <AbstractCdkResourceCheck .ArgumentTrace > argTrace = getArgument (ctx , (CallExpression ) expression , argName );
200
- if (argTrace .isEmpty ()) {
201
- return true ;
202
- }
203
-
204
- return argTrace .filter (trace -> trace .hasExpression (sensitiveValuePredicate )).isPresent ();
205
- };
206
- }
207
75
208
76
}
0 commit comments