From dab95355622604cb3f1d713ced02abd49c402893 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas Date: Tue, 23 Jul 2019 16:03:09 +0200 Subject: [PATCH 1/2] SONARPY-354 Migrate BackslashInStringCheck --- .../python/checks/BackslashInStringCheck.java | 26 +++++++++++-------- .../resources/checks/backslashInString.py | 4 +++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/python-checks/src/main/java/org/sonar/python/checks/BackslashInStringCheck.java b/python-checks/src/main/java/org/sonar/python/checks/BackslashInStringCheck.java index 2443ca458b..4e370aa80e 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/BackslashInStringCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/BackslashInStringCheck.java @@ -19,13 +19,13 @@ */ package org.sonar.python.checks; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; -import java.util.Collections; -import java.util.Set; +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.PyElementTypes; +import com.jetbrains.python.psi.PyStringLiteralExpression; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; -import org.sonar.python.api.PythonTokenType; +import org.sonar.python.SubscriptionContext; @Rule(key = "S1717") public class BackslashInStringCheck extends PythonCheck { @@ -34,13 +34,17 @@ public class BackslashInStringCheck extends PythonCheck { private static final String VALID_ESCAPED_CHARACTERS = "abfnrtvxnNrtuU\\'\"0123456789\n\r"; @Override - public Set subscribedKinds() { - return Collections.singleton(PythonTokenType.STRING); + public void initialize(Context context) { + context.registerSyntaxNodeConsumer(PyElementTypes.STRING_LITERAL_EXPRESSION, ctx -> { + PyStringLiteralExpression expression = (PyStringLiteralExpression) ctx.syntaxNode(); + for (ASTNode stringNode : expression.getStringNodes()) { + checkLiteral(ctx, stringNode.getPsi()); + } + }); } - @Override - public void visitNode(AstNode node) { - String string = node.getTokenOriginalValue(); + public void checkLiteral(SubscriptionContext ctx, PsiElement literal) { + String string = literal.getNode().getText(); int length = string.length(); boolean isEscaped = false; boolean inPrefix = true; @@ -54,7 +58,7 @@ public void visitNode(AstNode node) { } } else { if (isEscaped && VALID_ESCAPED_CHARACTERS.indexOf(c) == -1 && !isBackslashedSpaceAfterInlineMarkup(isThreeQuotes, string, i, c)) { - addIssue(node, MESSAGE); + ctx.addIssue(literal, MESSAGE); } isEscaped = c == '\\' && !isEscaped; } diff --git a/python-checks/src/test/resources/checks/backslashInString.py b/python-checks/src/test/resources/checks/backslashInString.py index 1f6949030e..6becbaee81 100644 --- a/python-checks/src/test/resources/checks/backslashInString.py +++ b/python-checks/src/test/resources/checks/backslashInString.py @@ -24,3 +24,7 @@ z = "*a*\ s" # Noncompliant z = """\ s""" # Noncompliant z = "" +re.compile(r'...' + r'\("abc ' + 'def"\)$') # Noncompliant + From 8bd11133f6027fe22befa7ee1b85011fcafe56d4 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas Date: Tue, 23 Jul 2019 16:03:31 +0200 Subject: [PATCH 2/2] SONARPY-354 Migrate CollapsibleIfStatementsCheck --- .../checks/CollapsibleIfStatementsCheck.java | 69 +++++++++---------- .../java/org/sonar/python/PythonCheck.java | 6 ++ 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/python-checks/src/main/java/org/sonar/python/checks/CollapsibleIfStatementsCheck.java b/python-checks/src/main/java/org/sonar/python/checks/CollapsibleIfStatementsCheck.java index 5dcc4fbca6..216ca61f2b 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/CollapsibleIfStatementsCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/CollapsibleIfStatementsCheck.java @@ -19,54 +19,53 @@ */ package org.sonar.python.checks; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.PyElementTypes; +import com.jetbrains.python.psi.PyIfPart; +import com.jetbrains.python.psi.PyIfStatement; +import com.jetbrains.python.psi.PyStatement; +import com.jetbrains.python.psi.PyStatementList; +import javax.annotation.CheckForNull; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; -import org.sonar.python.api.PythonGrammar; -import org.sonar.python.api.PythonKeyword; -import org.sonar.sslr.ast.AstSelect; -@Rule(key = CollapsibleIfStatementsCheck.CHECK_KEY) +@Rule(key = "S1066") public class CollapsibleIfStatementsCheck extends PythonCheck { - public static final String CHECK_KEY = "S1066"; private static final String MESSAGE = "Merge this if statement with the enclosing one."; @Override - public Set subscribedKinds() { - return Collections.singleton(PythonGrammar.IF_STMT); - } + public void initialize(Context context) { + context.registerSyntaxNodeConsumer(PyElementTypes.IF_STATEMENT, ctx -> { + PyIfStatement ifStatement = (PyIfStatement) ctx.syntaxNode(); - @Override - public void visitNode(AstNode node) { - AstNode suite = node.getLastChild(PythonGrammar.SUITE); - if (suite.getPreviousSibling().getPreviousSibling().is(PythonKeyword.ELSE)) { - return; - } - AstNode singleIfChild = singleIfChild(suite); - if (singleIfChild != null && !hasElseOrElif(singleIfChild)) { - addIssue(singleIfChild.getToken(), MESSAGE) - .secondary(node.getFirstChild(), "enclosing"); - } - } + if (ifStatement.getElsePart() != null) { + return; + } + + PyIfPart[] elifParts = ifStatement.getElifParts(); + PyIfPart lastIfPart = elifParts.length == 0 ? ifStatement.getIfPart() : elifParts[elifParts.length - 1]; - private static boolean hasElseOrElif(AstNode ifNode) { - return ifNode.hasDirectChildren(PythonKeyword.ELIF) || ifNode.hasDirectChildren(PythonKeyword.ELSE); + PyIfStatement singleIfChild = singleIfChild(lastIfPart.getStatementList()); + if (singleIfChild != null && singleIfChild.getElifParts().length == 0 && singleIfChild.getElsePart() == null) { + ctx.addIssue(ifKeyword(singleIfChild), MESSAGE) + .secondary(ifKeyword(ifStatement), "enclosing"); + } + }); } - private static AstNode singleIfChild(AstNode suite) { - List statements = suite.getChildren(PythonGrammar.STATEMENT); - if (statements.size() == 1) { - AstSelect nestedIf = statements.get(0).select() - .children(PythonGrammar.COMPOUND_STMT) - .children(PythonGrammar.IF_STMT); - if (nestedIf.size() == 1) { - return nestedIf.get(0); + @CheckForNull + private static PyIfStatement singleIfChild(PyStatementList statementList) { + PyStatement[] statements = statementList.getStatements(); + if (statements.length == 1) { + PyStatement statement = statements[0]; + if (statement.getNode().getElementType() == PyElementTypes.IF_STATEMENT) { + return (PyIfStatement) statement; } } return null; } + + private static PsiElement ifKeyword(PyIfStatement ifStatement) { + return ifStatement.getNode().findLeafElementAt(0).getPsi(); + } } diff --git a/python-squid/src/main/java/org/sonar/python/PythonCheck.java b/python-squid/src/main/java/org/sonar/python/PythonCheck.java index 80a00be862..98637a9307 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonCheck.java +++ b/python-squid/src/main/java/org/sonar/python/PythonCheck.java @@ -19,6 +19,7 @@ */ package org.sonar.python; +import com.intellij.psi.PsiElement; import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; import java.util.ArrayList; @@ -96,6 +97,11 @@ public PreciseIssue secondary(IssueLocation issueLocation) { return this; } + public PreciseIssue secondary(PsiElement element, @Nullable String message) { + secondaryLocations.add(IssueLocation.preciseLocation(element, message)); + return this; + } + public List secondaryLocations() { return secondaryLocations; }