Skip to content

Commit 9e64039

Browse files
joke1196sonartech
authored andcommitted
SONARPY-3949 Migrated rule S2115, S5828, S6727, S6882, S6887 to typeV2 (#991)
GitOrigin-RevId: 712dcaf53f7ce55d7259650cf0efe27b5d7cf5b6
1 parent a4f7669 commit 9e64039

25 files changed

+444
-43
lines changed

python-checks/src/main/java/org/sonar/python/checks/DbNoPasswordCheck.java

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@
1616
*/
1717
package org.sonar.python.checks;
1818

19-
import java.util.Arrays;
2019
import java.util.List;
2120
import java.util.regex.Matcher;
2221
import java.util.regex.Pattern;
2322
import org.sonar.check.Rule;
2423
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2524
import org.sonar.plugins.python.api.SubscriptionContext;
26-
import org.sonar.plugins.python.api.symbols.Symbol;
2725
import org.sonar.plugins.python.api.tree.Argument;
2826
import org.sonar.plugins.python.api.tree.AssignmentStatement;
2927
import org.sonar.plugins.python.api.tree.CallExpression;
@@ -34,6 +32,8 @@
3432
import org.sonar.plugins.python.api.tree.StringLiteral;
3533
import org.sonar.plugins.python.api.tree.Tree;
3634
import org.sonar.plugins.python.api.tree.Tree.Kind;
35+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
36+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
3737

3838
import static org.sonar.plugins.python.api.tree.Tree.Kind.ASSIGNMENT_STMT;
3939
import static org.sonar.plugins.python.api.tree.Tree.Kind.CALL_EXPR;
@@ -49,15 +49,19 @@ public class DbNoPasswordCheck extends PythonSubscriptionCheck {
4949

5050
private static final String MESSAGE = "Add password protection to this database.";
5151

52-
private static final List<String> CONNECT_METHODS = Arrays.asList(
53-
"mysql.connector.connect",
54-
"mysql.connector.connection.MySQLConnection",
55-
"pymysql.connections.connect",
56-
"psycopg2.connect",
57-
"pgdb.connect.connect",
58-
"pg.DB",
59-
"pg.connect"
60-
);
52+
private static final TypeMatcher PG_MATCHER = TypeMatchers.any(
53+
TypeMatchers.isType("pg.db.DB"),
54+
TypeMatchers.isType("pg.connect"));
55+
56+
private static final TypeMatcher CONNECT_MATCHER = TypeMatchers.any(
57+
TypeMatchers.isType("mysql.connector.connect"),
58+
TypeMatchers.isType("mysql.connector.connection.MySQLConnection"),
59+
TypeMatchers.isType("pymysql.connections.connect"),
60+
TypeMatchers.isType("psycopg2.connect"),
61+
// pgdb.connect is a module whose connect function has FQN pgdb.connect.connect.
62+
// isType can't resolve it through the type table, so use withFQN to match on the FQN directly.
63+
TypeMatchers.withFQN("pgdb.connect.connect"),
64+
PG_MATCHER);
6165

6266
private static final Pattern CONNECTION_URI_PATTERN =
6367
Pattern.compile("^(?:postgresql|mysql|oracle|mssql)(?:\\+.+?)?://.+?(:.*)?@.+");
@@ -83,17 +87,17 @@ private static void checkDbUri(SubscriptionContext ctx) {
8387

8488
private static void checkDbApi(SubscriptionContext ctx) {
8589
CallExpression callExpr = (CallExpression) ctx.syntaxNode();
86-
Symbol symbol = callExpr.calleeSymbol();
87-
if (symbol != null && CONNECT_METHODS.contains(symbol.fullyQualifiedName())) {
88-
RegularArgument passwordArgument = getPasswordArgument(symbol.fullyQualifiedName(), callExpr.arguments());
89-
if (passwordArgument != null && isString(passwordArgument.expression(), "")) {
90-
ctx.addIssue(passwordArgument, MESSAGE);
91-
}
90+
if (!CONNECT_MATCHER.isTrueFor(callExpr.callee(), ctx)) {
91+
return;
92+
}
93+
boolean isPg = PG_MATCHER.isTrueFor(callExpr.callee(), ctx);
94+
RegularArgument passwordArgument = getPasswordArgument(isPg, callExpr.arguments());
95+
if (passwordArgument != null && isString(passwordArgument.expression(), "")) {
96+
ctx.addIssue(passwordArgument, MESSAGE);
9297
}
9398
}
9499

95-
private static RegularArgument getPasswordArgument(String method, List<Argument> arguments) {
96-
boolean isPg = method.startsWith("pg.");
100+
private static RegularArgument getPasswordArgument(boolean isPg, List<Argument> arguments) {
97101
String argumentKeyword = isPg ? "passwd" : "password";
98102
int passwordIndex = isPg ? 5 : 2;
99103
int positionalIndex = 0;

python-checks/src/main/java/org/sonar/python/checks/IncorrectParameterDatetimeConstructorsCheck.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919
import org.sonar.check.Rule;
2020
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2121
import org.sonar.plugins.python.api.SubscriptionContext;
22-
import org.sonar.plugins.python.api.symbols.Symbol;
2322
import org.sonar.plugins.python.api.tree.CallExpression;
2423
import org.sonar.plugins.python.api.tree.Expression;
2524
import org.sonar.plugins.python.api.tree.Name;
2625
import org.sonar.plugins.python.api.tree.NumericLiteral;
2726
import org.sonar.plugins.python.api.tree.RegularArgument;
2827
import org.sonar.plugins.python.api.tree.Tree;
2928
import org.sonar.plugins.python.api.tree.UnaryExpression;
29+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
30+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
3031
import org.sonar.python.checks.utils.Expressions;
3132
import org.sonar.python.tree.TreeUtils;
3233

@@ -36,6 +37,9 @@ public class IncorrectParameterDatetimeConstructorsCheck extends PythonSubscript
3637
private static final int MAX_YEAR = 9999;
3738
private static final String MESSAGE = "Provide a correct value for the `%s` parameter.";
3839
private static final String MESSAGE_SECONDARY_LOCATION = "An invalid value is assigned here.";
40+
private static final TypeMatcher DATE_MATCHER = TypeMatchers.isType("datetime.date");
41+
private static final TypeMatcher TIME_MATCHER = TypeMatchers.isType("datetime.time");
42+
private static final TypeMatcher DATETIME_MATCHER = TypeMatchers.isType("datetime.datetime");
3943

4044
@Override
4145
public void initialize(Context context) {
@@ -44,15 +48,12 @@ public void initialize(Context context) {
4448

4549
private static void checkCallExpr(SubscriptionContext context) {
4650
CallExpression callExpression = (CallExpression) context.syntaxNode();
47-
Symbol calleeSymbol = callExpression.calleeSymbol();
48-
if (calleeSymbol == null) {
49-
return;
50-
}
51-
if ("datetime.date".equals(calleeSymbol.fullyQualifiedName())) {
51+
Expression callee = callExpression.callee();
52+
if (DATE_MATCHER.isTrueFor(callee, context)) {
5253
checkDate(context, callExpression);
53-
} else if ("datetime.time".equals(calleeSymbol.fullyQualifiedName())) {
54+
} else if (TIME_MATCHER.isTrueFor(callee, context)) {
5455
checkTime(context, callExpression);
55-
} else if ("datetime.datetime".equals(calleeSymbol.fullyQualifiedName())) {
56+
} else if (DATETIME_MATCHER.isTrueFor(callee, context)) {
5657
checkDate(context, callExpression);
5758
checkTime(context, callExpression, 3);
5859
}

python-checks/src/main/java/org/sonar/python/checks/InvalidOpenModeCheck.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.sonar.check.Rule;
2424
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2525
import org.sonar.plugins.python.api.SubscriptionContext;
26-
import org.sonar.plugins.python.api.symbols.Symbol;
2726
import org.sonar.plugins.python.api.tree.Argument;
2827
import org.sonar.plugins.python.api.tree.CallExpression;
2928
import org.sonar.plugins.python.api.tree.Expression;
@@ -32,6 +31,8 @@
3231
import org.sonar.plugins.python.api.tree.StringElement;
3332
import org.sonar.plugins.python.api.tree.StringLiteral;
3433
import org.sonar.plugins.python.api.tree.Tree;
34+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
35+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
3536
import org.sonar.python.checks.utils.Expressions;
3637
import org.sonar.python.tree.TreeUtils;
3738

@@ -41,13 +42,13 @@ public class InvalidOpenModeCheck extends PythonSubscriptionCheck {
4142
private static final String VALID_MODES = "rwatb+Ux";
4243
private static final Pattern INVALID_CHARACTERS = Pattern.compile("[^" + VALID_MODES + "]");
4344
private static final String MESSAGE = "Fix this invalid mode string.";
45+
private static final TypeMatcher OPEN_MATCHER = TypeMatchers.isType("open");
4446

4547
@Override
4648
public void initialize(Context context) {
4749
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> {
4850
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
49-
Symbol calleeSymbol = callExpression.calleeSymbol();
50-
if (calleeSymbol == null || !"open".equals(calleeSymbol.fullyQualifiedName())) {
51+
if (!OPEN_MATCHER.isTrueFor(callExpression.callee(), ctx)) {
5152
return;
5253
}
5354
List<Argument> arguments = callExpression.arguments();

python-checks/src/main/java/org/sonar/python/checks/IsCloseAbsTolCheck.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@
2222
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2323
import org.sonar.plugins.python.api.SubscriptionContext;
2424
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
25-
import org.sonar.plugins.python.api.symbols.Symbol;
2625
import org.sonar.plugins.python.api.tree.CallExpression;
2726
import org.sonar.plugins.python.api.tree.Expression;
2827
import org.sonar.plugins.python.api.tree.Name;
2928
import org.sonar.plugins.python.api.tree.NumericLiteral;
3029
import org.sonar.plugins.python.api.tree.RegularArgument;
3130
import org.sonar.plugins.python.api.tree.Tree;
31+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
32+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
3233
import org.sonar.python.checks.utils.Expressions;
3334
import org.sonar.python.quickfix.TextEditUtils;
3435
import org.sonar.python.tree.TreeUtils;
@@ -39,6 +40,7 @@ public class IsCloseAbsTolCheck extends PythonSubscriptionCheck {
3940
private static final String MESSAGE = "Provide the \"abs_tol\" parameter when using \"math.isclose\" to compare a value to 0.";
4041
private static final String SECONDARY_LOCATION_MESSAGE = "This argument evaluates to zero.";
4142
private static final String QUICK_FIX_MESSAGE = "Add the \"abs_tol\" parameter.";
43+
private static final TypeMatcher MATH_ISCLOSE = TypeMatchers.isType("math.isclose");
4244

4345
@Override
4446
public void initialize(Context context) {
@@ -47,8 +49,7 @@ public void initialize(Context context) {
4749
}
4850

4951
private static void checkForIsCloseAbsTolArgument(SubscriptionContext ctx, CallExpression call) {
50-
Symbol symbol = call.calleeSymbol();
51-
if (symbol != null && "math.isclose".equals(symbol.fullyQualifiedName())
52+
if (MATH_ISCLOSE.isTrueFor(call.callee(), ctx)
5253
&& TreeUtils.argumentByKeyword("abs_tol", call.arguments()) == null) {
5354
RegularArgument firstArg = TreeUtils.nthArgumentOrKeyword(0, "a", call.arguments());
5455
RegularArgument secondArg = TreeUtils.nthArgumentOrKeyword(1, "b", call.arguments());

python-checks/src/main/java/org/sonar/python/checks/PytzTimeZoneInDatetimeConstructorCheck.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,24 @@
1818

1919
import java.util.Comparator;
2020
import java.util.List;
21-
import java.util.Optional;
2221
import org.sonar.check.Rule;
2322
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2423
import org.sonar.plugins.python.api.SubscriptionContext;
25-
import org.sonar.plugins.python.api.symbols.Symbol;
2624
import org.sonar.plugins.python.api.tree.CallExpression;
2725
import org.sonar.plugins.python.api.tree.Name;
2826
import org.sonar.plugins.python.api.tree.RegularArgument;
2927
import org.sonar.plugins.python.api.tree.Tree;
28+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
29+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
3030
import org.sonar.python.tree.TreeUtils;
3131

3232
@Rule(key = "S6887")
3333
public class PytzTimeZoneInDatetimeConstructorCheck extends PythonSubscriptionCheck {
3434

3535
private static final String MESSAGE = "Don't pass a \"pytz.timezone\" to the \"datetime.datetime\" constructor.";
3636
private static final String SECONDARY_MESSAGE = "The pytz.timezone is created here.";
37+
private static final TypeMatcher DATETIME_DATETIME = TypeMatchers.isType("datetime.datetime");
38+
private static final TypeMatcher PYTZ_TIMEZONE = TypeMatchers.isType("pytz.timezone");
3739

3840
@Override
3941
public void initialize(Context context) {
@@ -42,9 +44,8 @@ public void initialize(Context context) {
4244

4345
private static void checkCallExpression(SubscriptionContext context) {
4446
CallExpression callExpression = (CallExpression) context.syntaxNode();
45-
Symbol calleeSymbol = callExpression.calleeSymbol();
4647

47-
if (calleeSymbol != null && "datetime.datetime".equals(calleeSymbol.fullyQualifiedName())) {
48+
if (DATETIME_DATETIME.isTrueFor(callExpression.callee(), context)) {
4849
RegularArgument argument = TreeUtils.nthArgumentOrKeyword(7, "tzinfo", callExpression.arguments());
4950
if (argument == null) {
5051
return;
@@ -56,16 +57,15 @@ private static void checkCallExpression(SubscriptionContext context) {
5657
private static void checkArgument(RegularArgument argument, SubscriptionContext context) {
5758
if (argument.expression().is(Tree.Kind.CALL_EXPR)) {
5859
CallExpression callExpression = (CallExpression) argument.expression();
59-
Symbol calleeSymbol = callExpression.calleeSymbol();
60-
if (!(calleeSymbol != null && "pytz.timezone".equals(calleeSymbol.fullyQualifiedName()))) {
60+
if (!PYTZ_TIMEZONE.isTrueFor(callExpression.callee(), context)) {
6161
return;
6262
}
6363
context.addIssue(argument, MESSAGE);
6464
} else if (argument.expression().is(Tree.Kind.NAME)) {
6565
List<CallExpression> allSecondaryLocations = context.valuesAtLocation((Name) argument.expression()).stream()
6666
.filter(expression -> expression.is(Tree.Kind.CALL_EXPR))
6767
.map(CallExpression.class::cast)
68-
.filter(call -> Optional.ofNullable(call.calleeSymbol()).map(symbol ->"pytz.timezone".equals(symbol.fullyQualifiedName())).orElse(false))
68+
.filter(call -> PYTZ_TIMEZONE.isTrueFor(call.callee(), context))
6969
.sorted(Comparator.comparingInt(call -> call.firstToken().line()))
7070
.toList();
7171

python-frontend/src/main/resources/org/sonar/python/types/custom_protobuf/pg.connection.protobuf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
pg.connection�
3+
4+
Connectionpg.connection.Connection"*SonarPythonAnalyzerFakeStub.CustomStubBase*�
5+
querypg.connection.Connection.query"
6+
None*>
7+
self4
8+
pg.connection.Connection"pg.connection.Connection*)
9+
command
10+
builtins.str" builtins.str*
11+
args
12+
Any*q
13+
closepg.connection.Connection.close"
14+
None*>
15+
self4
16+
pg.connection.Connection"pg.connection.Connection*�
17+
__annotations__pg.connection.__annotations__W
18+
builtins.dict[builtins.str,Any]
19+
builtins.str" builtins.str
20+
Any"builtins.dict

python-frontend/src/main/resources/org/sonar/python/types/custom_protobuf/pg.db.protobuf

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
pg.db�
3+
DBpg.db.DB"*SonarPythonAnalyzerFakeStub.CustomStubBase*�
4+
__init__pg.db.DB.__init__"
5+
None*
6+
self
7+
pg.db.DB"pg.db.DB*R
8+
dbnameD
9+
Union[builtins.str,None]
10+
builtins.str" builtins.str
11+
None *P
12+
hostD
13+
Union[builtins.str,None]
14+
builtins.str" builtins.str
15+
None *(
16+
port
17+
builtins.int" builtins.int *O
18+
optD
19+
Union[builtins.str,None]
20+
builtins.str" builtins.str
21+
None *P
22+
userD
23+
Union[builtins.str,None]
24+
builtins.str" builtins.str
25+
None *R
26+
passwdD
27+
Union[builtins.str,None]
28+
builtins.str" builtins.str
29+
None *
30+
querypg.db.DB.query"
31+
None*
32+
self
33+
pg.db.DB"pg.db.DB*)
34+
command
35+
builtins.str" builtins.str*
36+
args
37+
Any*A
38+
closepg.db.DB.close"
39+
None*
40+
self
41+
pg.db.DB"pg.db.DB*�
42+
__annotations__pg.db.__annotations__W
43+
builtins.dict[builtins.str,Any]
44+
builtins.str" builtins.str
45+
Any"builtins.dict

0 commit comments

Comments
 (0)