Skip to content

Commit cdbd140

Browse files
joke1196sonartech
authored andcommitted
SONARPY-2523: S6735 Fix FP when providing a value for keyword "on" when "how=cross" is used (#362)
GitOrigin-RevId: 9d23ae434fc8066a87ddf714c52c73e1c6cf76c0
1 parent 3f19cd5 commit cdbd140

2 files changed

Lines changed: 23 additions & 6 deletions

File tree

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package org.sonar.python.checks;
1818

19+
import static org.sonar.python.tree.TreeUtils.toOptionalInstanceOfMapper;
20+
1921
import java.util.ArrayList;
2022
import java.util.EnumSet;
2123
import java.util.List;
@@ -30,6 +32,8 @@
3032
import org.sonar.plugins.python.api.symbols.Symbol;
3133
import org.sonar.plugins.python.api.tree.Argument;
3234
import org.sonar.plugins.python.api.tree.CallExpression;
35+
import org.sonar.plugins.python.api.tree.RegularArgument;
36+
import org.sonar.plugins.python.api.tree.StringLiteral;
3337
import org.sonar.plugins.python.api.tree.Tree;
3438
import org.sonar.python.quickfix.TextEditUtils;
3539
import org.sonar.python.tree.TreeUtils;
@@ -130,7 +134,8 @@ private static void missingArguments(String fullyQualifiedName, SubscriptionCont
130134
if (isArgumentMissing(fullyQualifiedName, Keywords.HOW, callExpression.arguments())) {
131135
missingKeywords.add(Keywords.HOW);
132136
}
133-
if (ON_KEYWORDS.stream().allMatch(keyword -> isArgumentMissing(fullyQualifiedName, keyword, callExpression.arguments()))) {
137+
if (ON_KEYWORDS.stream()
138+
.allMatch(keyword -> !isCrossJoin(fullyQualifiedName, callExpression) && isArgumentMissing(fullyQualifiedName, keyword, callExpression.arguments()))) {
134139
missingKeywords.add(Keywords.ON);
135140
}
136141
if (isArgumentMissing(fullyQualifiedName, Keywords.VALIDATE, callExpression.arguments())) {
@@ -146,6 +151,15 @@ private static void missingArguments(String fullyQualifiedName, SubscriptionCont
146151
}
147152
}
148153

154+
private static boolean isCrossJoin(String fullyQualifiedName, CallExpression callExpression) {
155+
return TreeUtils.nthArgumentOrKeywordOptional(Keywords.HOW.getArgumentPosition(fullyQualifiedName), Keywords.HOW.getKeyword(), callExpression.arguments())
156+
.map(RegularArgument::expression)
157+
.flatMap(toOptionalInstanceOfMapper(StringLiteral.class))
158+
.map(StringLiteral::trimmedQuotesValue)
159+
.map("cross"::equals)
160+
.orElse(false);
161+
}
162+
149163
private static boolean isArgumentMissing(String fullyQualfiedName, Keywords keyword, List<Argument> arguments) {
150164
return Optional.of(keyword)
151165
.map(kw -> TreeUtils.nthArgumentOrKeyword(kw.getArgumentPosition(fullyQualfiedName), kw.getKeyword(), arguments))

python-checks/src/test/resources/checks/pandasAddMergeParameters.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ def non_compliant_merge_1():
1616

1717
_ = age_df.merge(name_df, on="user_id", validate="1:1") # Noncompliant {{Specify the "how" parameter of this merge.}}
1818

19-
_ = age_df.merge(name_df, how="cross", validate="1:1") # Noncompliant {{Specify the "on" parameter of this merge.}}
2019

2120
_ = pd.merge(age_df, name_df, on="user_id") # Noncompliant {{Specify the "how" and "validate" parameters of this merge.}}
2221

@@ -30,8 +29,6 @@ def non_compliant_merge_1():
3029

3130
_ = pd.merge(age_df, name_df, on="user_id", validate="1:1") # Noncompliant {{Specify the "how" parameter of this merge.}}
3231

33-
_ = pd.merge(age_df, name_df, how="cross", # Noncompliant {{Specify the "on" parameter of this merge.}}
34-
validate="1:1")
3532

3633
_ = age_df.join(name_df) # Noncompliant {{Specify the "how", "on" and "validate" parameters of this join.}}
3734

@@ -45,7 +42,6 @@ def non_compliant_merge_1():
4542

4643
_ = age_df.join(name_df, on="user_id", validate="1:1") # Noncompliant {{Specify the "how" parameter of this join.}}
4744

48-
_ = age_df.join(name_df, how="cross", validate="1:1") # Noncompliant {{Specify the "on" parameter of this join.}}
4945

5046

5147
def non_compliant_2():
@@ -67,11 +63,11 @@ def non_compliant_2():
6763

6864
_ = merge(age_df, name_df, on="user_id", validate="1:1") # Noncompliant {{Specify the "how" parameter of this merge.}}
6965

70-
_ = merge(age_df, name_df, how="cross", validate="1:1") # Noncompliant {{Specify the "on" parameter of this merge.}}
7166

7267

7368
def compliant_1(xx):
7469
import pandas as pd
70+
from pandas import merge
7571

7672
age_df = pd.DataFrame({"user_id": [1, 2, 4], "age": [42, 45, 35]})
7773
name_df = pd.DataFrame({"user_id": [1, 2, 3, 4], "name": ["a", "b", "c", "d"]})
@@ -98,6 +94,13 @@ def compliant_1(xx):
9894

9995
_ = pd.merge(age_df, name_df, right_on='cat', how='left', validate='m:m')
10096

97+
_ = pd.merge(age_df, name_df, how="cross", # Cross should not provide the on argument
98+
validate="1:1")
99+
100+
_ = age_df.join(name_df, how="cross", validate="1:1") # Cross should not provide the on argument
101+
102+
_ = age_df.merge(name_df, how="cross", validate="1:1") # Cross should not provide the on argument
103+
101104
_ = age_df.merge(name_df, right_on='cat', how='left', validate='m:m')
102105

103106
_ = age_df.merge(name_df, right_on='cat', how='left', validate='m:m')

0 commit comments

Comments
 (0)