Skip to content

Commit 7d22c16

Browse files
joke1196sonartech
authored andcommitted
SONARPY-4139 Migrated the usage of inferred types to type matchers (#1098)
GitOrigin-RevId: e70ef34bb7a4905a1cee5c121f032a08938af91a
1 parent 177a2ae commit 7d22c16

10 files changed

Lines changed: 111 additions & 88 deletions

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@
2323
import org.sonar.plugins.python.api.tree.ParenthesizedExpression;
2424
import org.sonar.plugins.python.api.tree.Tree;
2525
import org.sonar.plugins.python.api.tree.UnaryExpression;
26-
import org.sonar.python.types.InferredTypes;
26+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
27+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
2728

2829
@Rule(key = "S2761")
2930

3031
public class DoublePrefixOperatorCheck extends PythonSubscriptionCheck {
3132

3233
private static final String MESSAGE = "Use the \"%s\" operator just once or not at all.";
3334
private static final String MESSAGE_NOT = "Use the \"bool()\" builtin function instead of calling \"not\" twice.";
35+
private static final TypeMatcher IS_INT = TypeMatchers.isObjectOfType("builtins.int");
3436

3537
@Override
3638
public void initialize(Context context) {
@@ -52,7 +54,7 @@ private static void doubleInversionCheck(SubscriptionContext ctx, UnaryExpressio
5254
if (invertedExpr.is(Tree.Kind.NOT)) {
5355
ctx.addIssue(original, MESSAGE_NOT);
5456
} else {
55-
if (((UnaryExpression) invertedExpr).expression().type() == InferredTypes.INT) {
57+
if (IS_INT.isTrueFor(((UnaryExpression) invertedExpr).expression(), ctx)) {
5658
ctx.addIssue(original, String.format(MESSAGE, original.operator().value()));
5759
}
5860
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
import org.sonar.plugins.python.api.tree.IsExpression;
3030
import org.sonar.plugins.python.api.tree.Name;
3131
import org.sonar.plugins.python.api.tree.Tree;
32-
import org.sonar.plugins.python.api.types.BuiltinTypes;
3332
import org.sonar.python.quickfix.TextEditUtils;
3433
import org.sonar.python.tree.TreeUtils;
34+
import org.sonar.plugins.python.api.types.BuiltinTypes;
3535
import org.sonar.plugins.python.api.types.v2.PythonType;
3636
import org.sonar.python.types.v2.TypeCheckBuilder;
3737
import org.sonar.python.types.v2.TypeChecker;

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

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
package org.sonar.python.checks;
1818

1919
import java.util.ArrayList;
20+
import java.util.HashSet;
2021
import java.util.List;
21-
import java.util.Objects;
2222
import java.util.Optional;
2323
import java.util.Set;
24-
import java.util.stream.Collectors;
2524
import org.sonar.check.Rule;
2625
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2726
import org.sonar.plugins.python.api.SubscriptionContext;
@@ -34,22 +33,23 @@
3433
import org.sonar.plugins.python.api.tree.Tree;
3534
import org.sonar.plugins.python.api.tree.YieldStatement;
3635
import org.sonar.plugins.python.api.types.BuiltinTypes;
36+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
37+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
3738
import org.sonar.python.quickfix.TextEditUtils;
3839
import org.sonar.python.tree.FunctionDefImpl;
39-
import org.sonar.python.types.InferredTypes;
4040

4141
@Rule(key = "S6538")
4242
public class MandatoryFunctionReturnTypeHintCheck extends PythonSubscriptionCheck {
4343

4444
public static final String MESSAGE = "Add a return type hint to this function declaration.";
4545
public static final String CONSTRUCTOR_MESSAGE = "Annotate the return type of this constructor with `None`.";
46-
private static final List<String> SUPPORTED_TYPES = List.of(
47-
BuiltinTypes.STR,
48-
BuiltinTypes.NONE_TYPE,
49-
BuiltinTypes.BOOL,
50-
BuiltinTypes.COMPLEX,
51-
BuiltinTypes.FLOAT,
52-
BuiltinTypes.INT);
46+
private static final List<SupportedReturnType> SUPPORTED_TYPES = List.of(
47+
new SupportedReturnType(TypeMatchers.isObjectOfType("builtins.str"), BuiltinTypes.STR),
48+
new SupportedReturnType(TypeMatchers.isObjectOfType("NoneType"), "None"),
49+
new SupportedReturnType(TypeMatchers.isObjectOfType("builtins.bool"), BuiltinTypes.BOOL),
50+
new SupportedReturnType(TypeMatchers.isObjectOfType("builtins.complex"), BuiltinTypes.COMPLEX),
51+
new SupportedReturnType(TypeMatchers.isObjectOfType("builtins.float"), BuiltinTypes.FLOAT),
52+
new SupportedReturnType(TypeMatchers.isObjectOfType("builtins.int"), BuiltinTypes.INT));
5353

5454
@Override
5555
public void initialize(Context context) {
@@ -78,7 +78,7 @@ private static void raiseIssueForReturnType(SubscriptionContext ctx, Name functi
7878
ReturnStatementVisitor returnStatementVisitor = new ReturnStatementVisitor();
7979
functionDef.body().accept(returnStatementVisitor);
8080
if (!returnStatementVisitor.returnStatements.isEmpty()) {
81-
addQuickFixForReturnType(issue, functionDef, returnStatementVisitor.returnStatements);
81+
addQuickFixForReturnType(ctx, issue, functionDef, returnStatementVisitor.returnStatements);
8282
} else if (returnStatementVisitor.yieldStatements.isEmpty()) {
8383
addQuickFixForNoneType(issue, functionDef);
8484
}
@@ -91,26 +91,39 @@ private static void addQuickFixForNoneType(PreciseIssue issue, FunctionDef funct
9191
issue.addQuickFix(quickFix);
9292
}
9393

94-
private static void addQuickFixForReturnType(PreciseIssue issue, FunctionDef functionDef, List<ReturnStatement> statements) {
95-
Set<String> returnTypes = statements.stream()
96-
.flatMap(stmts -> stmts.expressions().stream())
97-
.map(Expression::type)
98-
.map(InferredTypes::typeName)
99-
.filter(Objects::nonNull)
100-
.collect(Collectors.toSet());
94+
private static void addQuickFixForReturnType(SubscriptionContext ctx, PreciseIssue issue, FunctionDef functionDef, List<ReturnStatement> statements) {
95+
Set<String> returnTypes = collectSupportedReturnTypeAnnotations(statements, ctx);
10196
if (returnTypes.size() == 1) {
102-
String typeName = returnTypes.stream().iterator().next();
103-
if (SUPPORTED_TYPES.contains(typeName)) {
104-
PythonQuickFix quickFix = PythonQuickFix.newQuickFix(MandatoryFunctionReturnTypeHintCheck.MESSAGE)
105-
.addTextEdit(TextEditUtils.insertAfter(functionDef.rightPar(), String.format(" -> %s", fixTypeName(typeName))))
106-
.build();
107-
issue.addQuickFix(quickFix);
97+
String annotation = returnTypes.iterator().next();
98+
PythonQuickFix quickFix = PythonQuickFix.newQuickFix(MandatoryFunctionReturnTypeHintCheck.MESSAGE)
99+
.addTextEdit(TextEditUtils.insertAfter(functionDef.rightPar(), String.format(" -> %s", annotation)))
100+
.build();
101+
issue.addQuickFix(quickFix);
102+
}
103+
}
104+
105+
private static Set<String> collectSupportedReturnTypeAnnotations(List<ReturnStatement> statements, SubscriptionContext ctx) {
106+
Set<String> returnTypes = new HashSet<>();
107+
for (ReturnStatement stmt : statements) {
108+
for (Expression expression : stmt.expressions()) {
109+
Optional<String> annotation = supportedReturnTypeAnnotation(expression, ctx);
110+
if (annotation.isEmpty()) {
111+
return Set.of();
112+
}
113+
returnTypes.add(annotation.get());
108114
}
109115
}
116+
return returnTypes;
117+
}
118+
119+
private static Optional<String> supportedReturnTypeAnnotation(Expression expression, SubscriptionContext ctx) {
120+
return SUPPORTED_TYPES.stream()
121+
.filter(supportedType -> supportedType.matcher().isTrueFor(expression, ctx))
122+
.map(SupportedReturnType::annotation)
123+
.findFirst();
110124
}
111125

112-
private static String fixTypeName(String typeName) {
113-
return typeName.equals(BuiltinTypes.NONE_TYPE) ? "None" : typeName;
126+
private record SupportedReturnType(TypeMatcher matcher, String annotation) {
114127
}
115128

116129
private static class ReturnStatementVisitor extends BaseTreeVisitor {

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

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,19 @@
1919
import java.util.ArrayList;
2020
import java.util.HashSet;
2121
import java.util.List;
22-
import java.util.Objects;
2322
import java.util.Optional;
2423
import java.util.Set;
2524
import org.sonar.check.Rule;
2625
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2726
import org.sonar.plugins.python.api.SubscriptionContext;
28-
import org.sonar.plugins.python.api.symbols.AmbiguousSymbol;
29-
import org.sonar.plugins.python.api.symbols.FunctionSymbol;
30-
import org.sonar.plugins.python.api.symbols.Symbol;
3127
import org.sonar.plugins.python.api.tree.CallExpression;
3228
import org.sonar.plugins.python.api.tree.QualifiedExpression;
3329
import org.sonar.plugins.python.api.tree.SubscriptionExpression;
3430
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;
3533
import org.sonar.python.tree.TreeUtils;
36-
import org.sonar.python.types.InferredTypes;
34+
import org.sonar.python.types.v2.matchers.InternalTypeMatchers;
3735

3836
@Rule(key = "S6742")
3937
public class PandasChainInstructionCheck extends PythonSubscriptionCheck {
@@ -42,6 +40,10 @@ public class PandasChainInstructionCheck extends PythonSubscriptionCheck {
4240
private static final int MAX_CHAIN_LENGTH = 7;
4341

4442
private static final String DATAFRAME_FQN = "pandas.core.frame.DataFrame";
43+
private static final TypeMatcher IS_DATAFRAME = TypeMatchers.isObjectOfType(DATAFRAME_FQN);
44+
private static final TypeMatcher IS_OR_CONTAINS_DATAFRAME = TypeMatchers.any(
45+
IS_DATAFRAME,
46+
InternalTypeMatchers.isAnyTypeInUnionSatisfying(IS_DATAFRAME));
4547

4648
private final Set<QualifiedExpression> visited = new HashSet<>();
4749

@@ -54,7 +56,7 @@ public void initialize(Context context) {
5456
private void checkChainedInstructions(SubscriptionContext ctx) {
5557
QualifiedExpression expr = (QualifiedExpression) ctx.syntaxNode();
5658
List<QualifiedExpression> chain = visitQualifier(expr, new ArrayList<>());
57-
if (chain.size() >= MAX_CHAIN_LENGTH && isValidPandasCall(chain)) {
59+
if (chain.size() >= MAX_CHAIN_LENGTH && isValidPandasCall(chain, ctx)) {
5860
ctx.addIssue(chain.iterator().next(), MESSAGE);
5961
}
6062
}
@@ -94,44 +96,21 @@ private static Optional<CallExpression> ignoreSubscriptionAndGetCallExpr(Subscri
9496
return Optional.empty();
9597
}
9698

97-
private static boolean isValidPandasCall(List<QualifiedExpression> chain) {
99+
private static boolean isValidPandasCall(List<QualifiedExpression> chain, SubscriptionContext ctx) {
98100
QualifiedExpression firstQualifiedExpression = chain.get(chain.size() - 1);
99101

100-
boolean isADataFrameMethodCall = Optional.ofNullable(firstQualifiedExpression.symbol())
101-
.map(Symbol::fullyQualifiedName)
102-
.filter(fqn -> fqn.startsWith(DATAFRAME_FQN))
103-
.isPresent();
104-
105-
boolean isAFunctionReturningADataFrame = Optional.ofNullable(firstQualifiedExpression.symbol())
106-
.flatMap(PandasChainInstructionCheck::isReturnTypeADataFrame)
107-
.orElse(false);
102+
boolean isAFunctionReturningADataFrame = firstQualifiedExpression.parent() instanceof CallExpression callExpression
103+
&& callExpression.callee() == firstQualifiedExpression
104+
&& IS_OR_CONTAINS_DATAFRAME.isTrueFor(callExpression, ctx);
108105

106+
// We do not resolve "DataFrame.pipe" as pipe is a generic on NDFrame.
107+
// As we get an unknown type on typeV2 we need to use the name instead
109108
boolean doesNotContainACallToPipe = chain.stream()
110-
.map(QualifiedExpression::symbol)
111-
.filter(Objects::nonNull)
112-
.map(Symbol::fullyQualifiedName)
113-
.filter(Objects::nonNull)
114-
.noneMatch((DATAFRAME_FQN + ".pipe")::equals);
115-
116-
boolean isADataFrame = DATAFRAME_FQN.equals(InferredTypes.fullyQualifiedTypeName(firstQualifiedExpression.qualifier().type()));
109+
.noneMatch(qe -> "pipe".equals(qe.name().name()));
117110

118-
return (isADataFrameMethodCall || isAFunctionReturningADataFrame || isADataFrame) && doesNotContainACallToPipe;
119-
}
111+
boolean isADataFrame = IS_OR_CONTAINS_DATAFRAME.isTrueFor(firstQualifiedExpression.qualifier(), ctx);
120112

121-
private static Optional<Boolean> isReturnTypeADataFrame(Symbol symbol) {
122-
return Optional.of(symbol)
123-
.filter(s -> s.is(Symbol.Kind.AMBIGUOUS))
124-
.map(AmbiguousSymbol.class::cast)
125-
.map(s -> s.alternatives().stream()
126-
.filter(a -> a.is(Symbol.Kind.FUNCTION))
127-
.map(FunctionSymbol.class::cast)
128-
.map(FunctionSymbol::annotatedReturnTypeName)
129-
.anyMatch(DATAFRAME_FQN::equals))
130-
.or(() -> Optional.of(symbol)
131-
.filter(s -> s.is(Symbol.Kind.FUNCTION))
132-
.map(FunctionSymbol.class::cast)
133-
.map(FunctionSymbol::annotatedReturnTypeName)
134-
.map(DATAFRAME_FQN::equals));
113+
return (isAFunctionReturningADataFrame || isADataFrame) && doesNotContainACallToPipe;
135114
}
136115

137116
}

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

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2727
import org.sonar.plugins.python.api.SubscriptionContext;
2828
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
29-
import org.sonar.plugins.python.api.symbols.Symbol;
29+
import org.sonar.plugins.python.api.symbols.v2.SymbolV2;
3030
import org.sonar.plugins.python.api.tree.CallExpression;
3131
import org.sonar.plugins.python.api.tree.Expression;
3232
import org.sonar.plugins.python.api.tree.ListLiteral;
@@ -35,10 +35,12 @@
3535
import org.sonar.plugins.python.api.tree.RegularArgument;
3636
import org.sonar.plugins.python.api.tree.StringLiteral;
3737
import org.sonar.plugins.python.api.tree.Tree;
38+
import org.sonar.plugins.python.api.types.v2.UnknownType;
39+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
40+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
3841
import org.sonar.python.quickfix.TextEditUtils;
3942
import org.sonar.python.tree.TreeUtils;
4043
import org.sonar.python.tree.TupleImpl;
41-
import org.sonar.python.types.InferredTypes;
4244

4345
import static org.sonar.python.checks.utils.Expressions.getAssignedName;
4446

@@ -48,6 +50,8 @@ public class SklearnCachedPipelineDontAccessTransformersCheck extends PythonSubs
4850
public static final String MESSAGE = "Avoid accessing transformers in a cached pipeline.";
4951
public static final String MESSAGE_SECONDARY = "The transformer is accessed here";
5052
public static final String MESSAGE_SECONDARY_CREATION = "The Pipeline is created here";
53+
private static final TypeMatcher IS_PIPELINE = TypeMatchers.isType("sklearn.pipeline.Pipeline");
54+
private static final TypeMatcher IS_MAKE_PIPELINE = TypeMatchers.isType("sklearn.pipeline.make_pipeline");
5155

5256
@Override
5357
public void initialize(Context context) {
@@ -56,14 +60,16 @@ public void initialize(Context context) {
5660

5761
private static void checkCallExpr(SubscriptionContext subscriptionContext) {
5862
CallExpression callExpression = (CallExpression) subscriptionContext.syntaxNode();
59-
Optional<PipelineCreation> pipelineCreationOptional = isPipelineCreation(callExpression);
63+
Optional<PipelineCreation> pipelineCreationOptional = isPipelineCreation(callExpression, subscriptionContext);
6064
if (pipelineCreationOptional.isEmpty()) {
6165
return;
6266
}
6367
PipelineCreation pipelineCreation = pipelineCreationOptional.get();
6468

6569
var memoryArgument = TreeUtils.argumentByKeyword("memory", callExpression.arguments());
66-
if (memoryArgument == null || memoryArgument.expression().is(Tree.Kind.NONE) || memoryArgument.expression().type() == InferredTypes.anyType()) {
70+
if (memoryArgument == null ||
71+
memoryArgument.expression().is(Tree.Kind.NONE) ||
72+
memoryArgument.expression().typeV2() instanceof UnknownType) {
6773
return;
6874
}
6975
var stepsArgument = TreeUtils.nthArgumentOrKeyword(0, "steps", callExpression.arguments());
@@ -149,7 +155,7 @@ private static Optional<PythonQuickFix> getQuickFix(Name pipelineBindingVariable
149155
}
150156

151157
private static Map<Tree, QualifiedExpression> symbolIsUsedInQualifiedExpression(Name name) {
152-
Symbol symbol = name.symbol();
158+
SymbolV2 symbol = name.symbolV2();
153159
if (symbol == null) {
154160
return new HashMap<>();
155161
}
@@ -166,16 +172,13 @@ private enum PipelineCreation {
166172
MAKE_PIPELINE
167173
}
168174

169-
private static Optional<PipelineCreation> isPipelineCreation(CallExpression callExpression) {
170-
return Optional.ofNullable(callExpression.calleeSymbol()).map(Symbol::fullyQualifiedName)
171-
.map(fqn -> {
172-
if ("sklearn.pipeline.Pipeline".equals(fqn)) {
173-
return PipelineCreation.PIPELINE;
174-
}
175-
if ("sklearn.pipeline.make_pipeline".equals(fqn)) {
176-
return PipelineCreation.MAKE_PIPELINE;
177-
}
178-
return null;
179-
});
175+
private static Optional<PipelineCreation> isPipelineCreation(CallExpression callExpression, SubscriptionContext ctx) {
176+
if (IS_PIPELINE.isTrueFor(callExpression.callee(), ctx)) {
177+
return Optional.of(PipelineCreation.PIPELINE);
178+
}
179+
if (IS_MAKE_PIPELINE.isTrueFor(callExpression.callee(), ctx)) {
180+
return Optional.of(PipelineCreation.MAKE_PIPELINE);
181+
}
182+
return Optional.empty();
180183
}
181184
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.sonar.plugins.python.api.SubscriptionContext;
2929
import org.sonar.plugins.python.api.symbols.v2.SymbolV2;
3030
import org.sonar.plugins.python.api.symbols.v2.UsageV2;
31-
import org.sonar.plugins.python.api.types.BuiltinTypes;
3231
import org.sonar.plugins.python.api.tree.AssignmentStatement;
3332
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
3433
import org.sonar.plugins.python.api.tree.ClassDef;
@@ -48,6 +47,7 @@
4847
import org.sonar.plugins.python.api.tree.StringLiteral;
4948
import org.sonar.plugins.python.api.tree.Tree;
5049
import org.sonar.plugins.python.api.tree.Tuple;
50+
import org.sonar.plugins.python.api.types.BuiltinTypes;
5151
import org.sonar.plugins.python.api.types.v2.ClassType;
5252
import org.sonar.plugins.python.api.types.v2.FunctionType;
5353
import org.sonar.plugins.python.api.types.v2.PythonType;

python-checks/src/test/java/org/sonar/python/checks/MandatoryFunctionReturnTypeHintCheckTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,19 @@ void no_quick_fix_generic_types() {
242242
PythonQuickFixVerifier.verifyNoQuickFixes(check, codeWithIssue);
243243
}
244244

245+
@Test
246+
void no_quick_fix_mixed_supported_and_unsupported_return_types() {
247+
String codeWithIssue = code(
248+
"class Foo:",
249+
" pass",
250+
"def foo(condition: bool):",
251+
" if condition:",
252+
" return 42",
253+
" return Foo()");
254+
MandatoryFunctionReturnTypeHintCheck check = new MandatoryFunctionReturnTypeHintCheck();
255+
PythonQuickFixVerifier.verifyNoQuickFixes(check, codeWithIssue);
256+
}
257+
245258
@Test
246259
void no_quick_fix_ambiguous_type() {
247260
String codeWithIssue = code(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def start():
77
# ^^^
88
d = not (not (not (not a))) # Noncompliant 3
99

10-
e = ~~~~~a # Noncompliant
10+
e = ~~~~~a # Noncompliant 4
1111
f = ~(((((~a))))) # Noncompliant {{Use the "~" operator just once or not at all.}}
1212

1313
g = not (a == b)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def non_compliant(df: pd.DataFrame, df2: DataFrame):
88
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
99
DataFrame().set_index("name").filter(like='joe', axis=0).groupby("team")["salary"].add(10).mean().round().to_parquet() # Noncompliant {{Refactor this long chain of instructions with "pandas.pipe"}}
1010
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11-
df.set_index("name").filter(like='joe', axis=0).groupby("team")["salary"].add(10).mean().round().to_parquet() # FN see SONARPY-1503
11+
df.set_index("name").filter(like='joe', axis=0).groupby("team")["salary"].add(10).mean().round().to_parquet() # Noncompliant {{Refactor this long chain of instructions with "pandas.pipe"}}
1212

1313
df2.set_index("name").filter(like='joe', axis=0).groupby("team")["salary"]["test"].add(10).mean().round().to_parquet() # Noncompliant {{Refactor this long chain of instructions with "pandas.pipe"}}
1414
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -38,4 +38,3 @@ def compliant(df: pd.DataFrame, my_function, something, df2: DataFrame):
3838
DataFrame().set_index("name").pipe(my_function).filter(like='joe', axis=0).groupby("team")["salary"].add(10).round().mean().to_json()
3939

4040
something.set_index("name").filter(like='joe', axis=0).groupby("team")["salary"].add(10).round().mean().to_parquet()
41-

0 commit comments

Comments
 (0)