diff --git a/.gitignore b/.gitignore index d876e7c540..30a9a7cd4c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,3 @@ Desktop.ini # ---- Sonar .sonar .scannerwork - -pycharm-community-* diff --git a/Jenkinsfile b/Jenkinsfile index bf0204dc7f..7fd5850f3d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,7 +14,6 @@ pipeline { SONARSOURCE_QA = 'true' MAVEN_TOOL = 'Maven 3.6.x' JDK_VERSION = 'Java 11' - PYCHARM_VERSION = '2019.1.3' } stages { stage('Notify') { @@ -55,19 +54,6 @@ pipeline { steps { withMaven(maven: MAVEN_TOOL) { mavenSetBuildVersion() - sh "curl -L -O https://download-cf.jetbrains.com/python/pycharm-community-${PYCHARM_VERSION}.tar.gz" - sh "tar xzf pycharm-community-${PYCHARM_VERSION}.tar.gz" - sh "rm pycharm-community-${PYCHARM_VERSION}.tar.gz" - dir("pycharm-community-$PYCHARM_VERSION/lib") { - runMaven(JDK_VERSION,"install:install-file -Dfile=extensions.jar -DgroupId=com.jetbrains.pycharm -DartifactId=extensions -Dversion=${PYCHARM_VERSION} -Dpackaging=jar") - runMaven(JDK_VERSION,"install:install-file -Dfile=openapi.jar -DgroupId=com.jetbrains.pycharm -DartifactId=openapi -Dversion=${PYCHARM_VERSION} -Dpackaging=jar") - runMaven(JDK_VERSION,"install:install-file -Dfile=platform-api.jar -DgroupId=com.jetbrains.pycharm -DartifactId=platform-api -Dversion=${PYCHARM_VERSION} -Dpackaging=jar") - runMaven(JDK_VERSION,"install:install-file -Dfile=platform-impl.jar -DgroupId=com.jetbrains.pycharm -DartifactId=platform-impl -Dversion=${PYCHARM_VERSION} -Dpackaging=jar") - runMaven(JDK_VERSION,"install:install-file -Dfile=pycharm.jar -DgroupId=com.jetbrains.pycharm -DartifactId=pycharm -Dversion=${PYCHARM_VERSION} -Dpackaging=jar") - runMaven(JDK_VERSION,"install:install-file -Dfile=pycharm-pydev.jar -DgroupId=com.jetbrains.pycharm -DartifactId=pycharm-pydev -Dversion=${PYCHARM_VERSION} -Dpackaging=jar") - runMaven(JDK_VERSION,"install:install-file -Dfile=resources_en.jar -DgroupId=com.jetbrains.pycharm -DartifactId=resources_en -Dversion=${PYCHARM_VERSION} -Dpackaging=jar") - runMaven(JDK_VERSION,"install:install-file -Dfile=util.jar -DgroupId=com.jetbrains.pycharm -DartifactId=util -Dversion=${PYCHARM_VERSION} -Dpackaging=jar") - } runMaven(JDK_VERSION,"clean install -Dskip.its=true") } } diff --git a/README.md b/README.md index d74adb52ef..e7df0994d2 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,3 @@ SonarPython is a code analyzer for Python projects. Copyright 2011-2018 SonarSource. Licensed under the [GNU Lesser General Public License, Version 3.0](http://www.gnu.org/licenses/lgpl.txt) - -## Build - -Download required dependency: - -> ./tools/download-pycharm.sh - -Build the project using Maven: - -> mvn clean install diff --git a/its/plugin/src/test/java/com/sonar/python/it/plugin/MetricsTest.java b/its/plugin/src/test/java/com/sonar/python/it/plugin/MetricsTest.java index eaa6e401b9..f3c19f9d1a 100644 --- a/its/plugin/src/test/java/com/sonar/python/it/plugin/MetricsTest.java +++ b/its/plugin/src/test/java/com/sonar/python/it/plugin/MetricsTest.java @@ -85,7 +85,7 @@ public void project_level() { assertThat(getProjectMeasureAsInt(NCLOC)).isEqualTo(6); assertThat(getProjectMeasureAsInt(LINES)).isEqualTo(13); assertThat(getProjectMeasureAsInt(FILES)).isEqualTo(2); - assertThat(getProjectMeasureAsInt(STATEMENTS)).isEqualTo(6); + assertThat(getProjectMeasureAsInt(STATEMENTS)).isEqualTo(5); assertThat(getProjectMeasureAsInt(FUNCTIONS)).isEqualTo(1); assertThat(getProjectMeasureAsInt(CLASSES)).isEqualTo(0); // Documentation @@ -114,7 +114,7 @@ public void directory_level() { assertThat(getDirectoryMeasureAsInt(NCLOC)).isEqualTo(6); assertThat(getDirectoryMeasureAsInt(LINES)).isEqualTo(13); assertThat(getDirectoryMeasureAsInt(FILES)).isEqualTo(2); - assertThat(getDirectoryMeasureAsInt(STATEMENTS)).isEqualTo(6); + assertThat(getDirectoryMeasureAsInt(STATEMENTS)).isEqualTo(5); assertThat(getDirectoryMeasureAsInt(FUNCTIONS)).isEqualTo(1); assertThat(getDirectoryMeasureAsInt(CLASSES)).isEqualTo(0); // Documentation @@ -140,7 +140,7 @@ public void file_level() { assertThat(getFileMeasureAsInt(NCLOC)).isEqualTo(1); assertThat(getFileMeasureAsInt(LINES)).isEqualTo(6); assertThat(getFileMeasureAsInt(FILES)).isEqualTo(1); - assertThat(getFileMeasureAsInt(STATEMENTS)).isEqualTo(2); + assertThat(getFileMeasureAsInt(STATEMENTS)).isEqualTo(1); assertThat(getFileMeasureAsInt(FUNCTIONS)).isEqualTo(1); assertThat(getFileMeasureAsInt(CLASSES)).isEqualTo(0); // Documentation diff --git a/its/ruling/src/test/resources/expected/python-S100.json b/its/ruling/src/test/resources/expected/python-S100.json index 3765d90925..be5522ae24 100644 --- a/its/ruling/src/test/resources/expected/python-S100.json +++ b/its/ruling/src/test/resources/expected/python-S100.json @@ -1539,15 +1539,6 @@ 743, 762, 826, -911, -915, -926, -938, -941, -960, -969, -975, -982, 1116, 1137, 1151, @@ -2401,7 +2392,6 @@ 149, ], 'project:twisted-12.1.0/twisted/protocols/ident.py':[ -149, 162, 165, 171, diff --git a/its/ruling/src/test/resources/expected/python-S108.json b/its/ruling/src/test/resources/expected/python-S108.json index 22af468543..5031188367 100644 --- a/its/ruling/src/test/resources/expected/python-S108.json +++ b/its/ruling/src/test/resources/expected/python-S108.json @@ -57,10 +57,6 @@ 112, 144, ], -'project:numpy-1.16.4/numpy/core/einsumfunc.py':[ -832, -836, -], 'project:numpy-1.16.4/numpy/core/records.py':[ 179, ], @@ -128,9 +124,6 @@ 'project:twisted-12.1.0/twisted/internet/_threadedselect.py':[ 281, ], -'project:twisted-12.1.0/twisted/internet/tcp.py':[ -562, -], 'project:twisted-12.1.0/twisted/mail/test/test_pop3.py':[ 72, ], diff --git a/its/ruling/src/test/resources/expected/python-S1542.json b/its/ruling/src/test/resources/expected/python-S1542.json index 01c77206d1..4e0032adc5 100644 --- a/its/ruling/src/test/resources/expected/python-S1542.json +++ b/its/ruling/src/test/resources/expected/python-S1542.json @@ -1549,6 +1549,15 @@ ], 'project:twisted-12.1.0/twisted/internet/base.py':[ 856, +911, +915, +926, +938, +941, +960, +969, +975, +982, ], 'project:twisted-12.1.0/twisted/internet/default.py':[ 17, @@ -1606,6 +1615,15 @@ 'project:twisted-12.1.0/twisted/internet/inotify.py':[ 99, ], +'project:twisted-12.1.0/twisted/internet/iocpreactor/reactor.py':[ +172, +184, +193, +205, +], +'project:twisted-12.1.0/twisted/internet/iocpreactor/tcp.py':[ +145, +], 'project:twisted-12.1.0/twisted/internet/main.py':[ 20, ], @@ -2249,6 +2267,7 @@ 264, ], 'project:twisted-12.1.0/twisted/protocols/ident.py':[ +149, 153, ], 'project:twisted-12.1.0/twisted/protocols/loopback.py':[ @@ -2846,6 +2865,9 @@ 1065, 1108, 1141, +1374, +1382, +1390, ], 'project:twisted-12.1.0/twisted/test/test_iutils.py':[ 67, diff --git a/pom.xml b/pom.xml index 35079e48b5..f4f32c26d4 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,6 @@ - python-frontend python-squid python-checks sonar-python-plugin @@ -96,7 +95,6 @@ 3.0.0.1140 1.23 1.22 - 2019.1.3 diff --git a/python-checks/src/main/java/org/sonar/python/checks/AbstractFunctionNameCheck.java b/python-checks/src/main/java/org/sonar/python/checks/AbstractFunctionNameCheck.java index 7d4249c79b..a85b9b7c92 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/AbstractFunctionNameCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/AbstractFunctionNameCheck.java @@ -19,13 +19,17 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.jetbrains.python.PyStubElementTypes; -import com.jetbrains.python.psi.PyFunction; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.RuleProperty; +import org.sonar.python.api.PythonGrammar; public abstract class AbstractFunctionNameCheck extends AbstractNameCheck { + private static final String DEFAULT = "^[a-z_][a-z0-9_]{2,}$"; + private static final String MESSAGE = "Rename %s \"%s\" to match the regular expression %s."; @RuleProperty( key = "format", @@ -38,26 +42,25 @@ protected String format() { } @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyStubElementTypes.FUNCTION_DECLARATION, ctx -> { - PyFunction node = (PyFunction) ctx.syntaxNode(); - if (!shouldCheckFunctionDeclaration(node)) { - return; - } - ASTNode nameNode = node.getNameNode(); - if (nameNode == null) { - return; - } - String name = nameNode.getText(); - if (!pattern().matcher(name).matches()) { - String message = String.format("Rename %s \"%s\" to match the regular expression %s.", typeName(), name, format); - ctx.addIssue(nameNode.getPsi(), message); - } - }); + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.FUNCDEF); + } + + @Override + public void visitNode(AstNode astNode) { + if (!shouldCheckFunctionDeclaration(astNode)) { + return; + } + AstNode nameNode = astNode.getFirstChild(PythonGrammar.FUNCNAME); + String name = nameNode.getTokenValue(); + if (!pattern().matcher(name).matches()) { + String message = String.format(MESSAGE, typeName(), name, this.format); + addIssue(nameNode, message); + } } public abstract String typeName(); - public abstract boolean shouldCheckFunctionDeclaration(PyFunction function); + public abstract boolean shouldCheckFunctionDeclaration(AstNode astNode); } diff --git a/python-checks/src/main/java/org/sonar/python/checks/AfterJumpStatementCheck.java b/python-checks/src/main/java/org/sonar/python/checks/AfterJumpStatementCheck.java index 45cb56e945..6abae37ed1 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/AfterJumpStatementCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/AfterJumpStatementCheck.java @@ -19,52 +19,52 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.intellij.psi.tree.IElementType; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFileElementType; -import com.jetbrains.python.psi.PyStatement; -import com.jetbrains.python.psi.PyStatementList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; -import org.sonar.python.SubscriptionContext; +import org.sonar.python.api.PythonGrammar; -@Rule(key = "S1763") +@Rule(key = AfterJumpStatementCheck.CHECK_KEY) public class AfterJumpStatementCheck extends PythonCheck { - private static final Set JUMP_TYPES = new HashSet<>(Arrays.asList( - PyElementTypes.RETURN_STATEMENT, - PyElementTypes.RAISE_STATEMENT, - PyElementTypes.BREAK_STATEMENT, - PyElementTypes.CONTINUE_STATEMENT - )); + public static final String CHECK_KEY = "S1763"; + + private static final String MESSAGE = "Remove the code after this \"%s\"."; @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.STATEMENT_LIST, ctx -> { - PyStatementList statementList = (PyStatementList) ctx.syntaxNode(); - checkStatements(ctx, Arrays.asList(statementList.getStatements())); - }); - context.registerSyntaxNodeConsumer(PyFileElementType.INSTANCE, ctx -> { - PyFile pyFile = (PyFile) ctx.syntaxNode(); - checkStatements(ctx, pyFile.getStatements()); - }); + public Set subscribedKinds() { + return immutableSet( + PythonGrammar.RETURN_STMT, + PythonGrammar.RAISE_STMT, + PythonGrammar.BREAK_STMT, + PythonGrammar.CONTINUE_STMT + ); } - private static void checkStatements(SubscriptionContext ctx, List statements) { - for (PyStatement statement : statements.subList(0, Math.max(statements.size() - 1, 0))) { - if (JUMP_TYPES.contains(statement.getNode().getElementType())) { - ASTNode keyword = statement.getNode().findLeafElementAt(0); - ctx.addIssue(keyword.getPsi(), String.format( - "Refactor this piece of code to not have any dead code after this \"%s\".", keyword.getText())); + @Override + public void visitNode(AstNode node) { + AstNode simpleStatement = node.getParent(); + + AstNode nextSibling = simpleStatement.getNextSibling(); + if (nextSibling != null && nextSibling.getNextSibling() != null) { + raiseIssue(node); + return; + } + + AstNode stmtList = simpleStatement.getParent(); + if (stmtList.getParent().is(PythonGrammar.STATEMENT)){ + nextSibling = stmtList.getParent().getNextSibling(); + if (nextSibling != null && nextSibling.getNextSibling() != null){ + raiseIssue(node); } } } + private void raiseIssue(AstNode node) { + addIssue(node, String.format(MESSAGE, node.getTokenValue())); + } + } 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 4e370aa80e..2443ca458b 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.intellij.lang.ASTNode; -import com.intellij.psi.PsiElement; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyStringLiteralExpression; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; -import org.sonar.python.SubscriptionContext; +import org.sonar.python.api.PythonTokenType; @Rule(key = "S1717") public class BackslashInStringCheck extends PythonCheck { @@ -34,17 +34,13 @@ public class BackslashInStringCheck extends PythonCheck { private static final String VALID_ESCAPED_CHARACTERS = "abfnrtvxnNrtuU\\'\"0123456789\n\r"; @Override - 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()); - } - }); + public Set subscribedKinds() { + return Collections.singleton(PythonTokenType.STRING); } - public void checkLiteral(SubscriptionContext ctx, PsiElement literal) { - String string = literal.getNode().getText(); + @Override + public void visitNode(AstNode node) { + String string = node.getTokenOriginalValue(); int length = string.length(); boolean isEscaped = false; boolean inPrefix = true; @@ -58,7 +54,7 @@ public void checkLiteral(SubscriptionContext ctx, PsiElement literal) { } } else { if (isEscaped && VALID_ESCAPED_CHARACTERS.indexOf(c) == -1 && !isBackslashedSpaceAfterInlineMarkup(isThreeQuotes, string, i, c)) { - ctx.addIssue(literal, MESSAGE); + addIssue(node, MESSAGE); } isEscaped = c == '\\' && !isEscaped; } diff --git a/python-checks/src/main/java/org/sonar/python/checks/BackticksUsageCheck.java b/python-checks/src/main/java/org/sonar/python/checks/BackticksUsageCheck.java index 8435713a2c..2a1a918a71 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/BackticksUsageCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/BackticksUsageCheck.java @@ -19,16 +19,29 @@ */ package org.sonar.python.checks; -import com.jetbrains.python.PyElementTypes; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.PythonPunctuator; -@Rule(key = "BackticksUsage") +@Rule(key = BackticksUsageCheck.CHECK_KEY) public class BackticksUsageCheck extends PythonCheck { + public static final String CHECK_KEY = "BackticksUsage"; @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.REPR_EXPRESSION, ctx -> ctx.addIssue(ctx.syntaxNode(), "Use \"repr\" instead.")); + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.ATOM); + } + + @Override + public void visitNode(AstNode astNode) { + if (astNode.hasDirectChildren(PythonPunctuator.BACKTICK)) { + addIssue(astNode, "Use \"repr\" instead."); + } } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/BreakContinueOutsideLoopCheck.java b/python-checks/src/main/java/org/sonar/python/checks/BreakContinueOutsideLoopCheck.java index 1fc9a19675..398f7da19d 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/BreakContinueOutsideLoopCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/BreakContinueOutsideLoopCheck.java @@ -19,35 +19,41 @@ */ package org.sonar.python.checks; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyBreakStatement; -import com.jetbrains.python.psi.PyContinueStatement; -import com.jetbrains.python.psi.PyLoopStatement; -import com.jetbrains.python.psi.PyStatement; -import javax.annotation.Nullable; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; -import org.sonar.python.SubscriptionContext; +import org.sonar.python.api.PythonGrammar; -@Rule(key = "S1716") +@Rule(key = BreakContinueOutsideLoopCheck.CHECK_KEY) public class BreakContinueOutsideLoopCheck extends PythonCheck { + private static final String MESSAGE = "Remove this \"%s\" statement"; + public static final String CHECK_KEY = "S1716"; + @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.BREAK_STATEMENT, ctx -> { - PyBreakStatement node = (PyBreakStatement) ctx.syntaxNode(); - checkLoopStatement(ctx, node, node.getLoopStatement(), "Remove this \"break\" statement"); - }); - context.registerSyntaxNodeConsumer(PyElementTypes.CONTINUE_STATEMENT, ctx -> { - PyContinueStatement node = (PyContinueStatement) ctx.syntaxNode(); - checkLoopStatement(ctx, node, node.getLoopStatement(), "Remove this \"continue\" statement"); - }); + public Set subscribedKinds() { + return immutableSet(PythonGrammar.BREAK_STMT, PythonGrammar.CONTINUE_STMT); } - private static void checkLoopStatement(SubscriptionContext ctx, PyStatement node, @Nullable PyLoopStatement loopStatement, String message) { - if (loopStatement == null) { - ctx.addIssue(node.getNode().getFirstChildNode().getPsi(), message); + @Override + public void visitNode(AstNode node) { + AstNode currentParent = node.getParent(); + while (currentParent != null){ + if (currentParent.is(PythonGrammar.WHILE_STMT, PythonGrammar.FOR_STMT)){ + return; + } else if (currentParent.is(PythonGrammar.FUNCDEF, PythonGrammar.CLASSDEF)){ + raiseIssue(node); + return; + } + currentParent = currentParent.getParent(); } + raiseIssue(node); } + private void raiseIssue(AstNode node) { + addIssue(node, String.format(MESSAGE, node.getToken().getValue())); + } } + diff --git a/python-checks/src/main/java/org/sonar/python/checks/CheckUtils.java b/python-checks/src/main/java/org/sonar/python/checks/CheckUtils.java index 68292d186a..6130fcfe03 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/CheckUtils.java +++ b/python-checks/src/main/java/org/sonar/python/checks/CheckUtils.java @@ -19,9 +19,6 @@ */ package org.sonar.python.checks; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyFunction; import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; import java.util.List; @@ -40,10 +37,6 @@ private CheckUtils() { } - public static boolean isMethodDefinition(PyFunction function) { - return function.getContainingClass() != null; - } - public static boolean isMethodDefinition(AstNode node) { if (!node.is(PythonGrammar.FUNCDEF)) { return false; @@ -57,10 +50,6 @@ public static boolean isMethodDefinition(AstNode node) { return parent != null && parent.is(PythonGrammar.CLASSDEF); } - public static boolean isMethodOfNonDerivedClass(PyFunction function) { - return isMethodDefinition(function) && !classHasInheritance(function.getContainingClass()); - } - public static boolean isMethodOfNonDerivedClass(AstNode node) { return isMethodDefinition(node) && !classHasInheritance(node.getFirstAncestor(PythonGrammar.CLASSDEF)); } @@ -89,14 +78,6 @@ public static boolean insideFunction(AstNode astNode, AstNode funcDef) { return astNode.getFirstAncestor(PythonGrammar.FUNCDEF).equals(funcDef); } - public static boolean classHasInheritance(PyClass node) { - PyExpression[] superClassExpressions = node.getSuperClassExpressions(); - if (superClassExpressions.length == 0) { - return false; - } - return superClassExpressions.length != 1 || !"object".equals(superClassExpressions[0].getText()); - } - public static boolean classHasInheritance(AstNode classDef) { AstNode inheritanceClause = classDef.getFirstChild(PythonGrammar.ARGLIST); if (inheritanceClause == null) { diff --git a/python-checks/src/main/java/org/sonar/python/checks/ClassComplexityCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ClassComplexityCheck.java index 0df4863010..ce0c3bc150 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ClassComplexityCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ClassComplexityCheck.java @@ -19,12 +19,14 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.jetbrains.python.PyStubElementTypes; -import com.jetbrains.python.psi.PyClass; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonGrammar; import org.sonar.python.metrics.ComplexityVisitor; @Rule(key = "ClassComplexity") @@ -36,18 +38,18 @@ public class ClassComplexityCheck extends PythonCheck { int maximumClassComplexityThreshold = DEFAULT_MAXIMUM_CLASS_COMPLEXITY_THRESHOLD; @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyStubElementTypes.CLASS_DECLARATION, ctx -> { - PyClass node = (PyClass) ctx.syntaxNode(); - int complexity = ComplexityVisitor.complexity(node); - if (complexity > maximumClassComplexityThreshold) { - String message = String.format(MESSAGE, complexity, maximumClassComplexityThreshold); - ASTNode nameNode = node.getNameNode(); - if (nameNode != null) { - ctx.addIssue(nameNode.getPsi(), message).withCost(complexity - maximumClassComplexityThreshold); - } - } - }); + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.CLASSDEF); + } + + @Override + public void visitNode(AstNode node) { + int complexity = ComplexityVisitor.complexity(node); + if (complexity > maximumClassComplexityThreshold) { + String message = String.format(MESSAGE, complexity, maximumClassComplexityThreshold); + addIssue(node.getFirstChild(PythonGrammar.CLASSNAME), message) + .withCost(complexity - maximumClassComplexityThreshold); + } } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/ClassNameCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ClassNameCheck.java index c5690b255d..c23f9d2d4d 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ClassNameCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ClassNameCheck.java @@ -19,15 +19,20 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyClass; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; +import org.sonar.python.api.PythonGrammar; -@Rule(key = "S101") +@Rule(key = ClassNameCheck.CHECK_KEY) public class ClassNameCheck extends AbstractNameCheck { + + public static final String CHECK_KEY = "S101"; private static final String DEFAULT = "^[A-Z_][a-zA-Z0-9]+$"; + private static final String MESSAGE = "Rename class \"%s\" to match the regular expression %s."; @RuleProperty( key = "format", @@ -40,18 +45,18 @@ protected String format() { } @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.CLASS_DECLARATION, ctx -> { - ASTNode classNameNode = ((PyClass) ctx.syntaxNode()).getNameNode(); - if (classNameNode == null) { - return; - } - String className = classNameNode.getText(); - if (!pattern().matcher(className).matches()) { - String message = String.format("Rename class \"%s\" to match the regular expression %s.", className, format); - ctx.addIssue(classNameNode.getPsi(), message); - } - }); + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.CLASSDEF); + } + + @Override + public void visitNode(AstNode astNode) { + AstNode classNameNode = astNode.getFirstChild(PythonGrammar.CLASSNAME); + String className = classNameNode.getTokenValue(); + if (!pattern().matcher(className).matches()) { + String message = String.format(MESSAGE, className, format); + addIssue(classNameNode, message); + } } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/CognitiveComplexityFunctionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/CognitiveComplexityFunctionCheck.java index 83963b7414..31163e8ddf 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/CognitiveComplexityFunctionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/CognitiveComplexityFunctionCheck.java @@ -19,22 +19,23 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyFunction; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.python.IssueLocation; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonGrammar; import org.sonar.python.metrics.CognitiveComplexityVisitor; -@Rule(key = "S3776") +@Rule(key = CognitiveComplexityFunctionCheck.CHECK_KEY) public class CognitiveComplexityFunctionCheck extends PythonCheck { private static final String MESSAGE = "Refactor this function to reduce its Cognitive Complexity from %s to the %s allowed."; + public static final String CHECK_KEY = "S3776"; private static final int DEFAULT_THRESHOLD = 15; @RuleProperty( @@ -48,24 +49,23 @@ public void setThreshold(int threshold) { } @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.FUNCTION_DECLARATION, ctx -> { - PyFunction function = (PyFunction) ctx.syntaxNode(); - if (PsiTreeUtil.getParentOfType(function, PyFunction.class) != null) { - return; - } - List secondaryLocations = new ArrayList<>(); - int complexity = CognitiveComplexityVisitor.complexity(function, (node, message) -> secondaryLocations.add(IssueLocation.preciseLocation(node, message))); - if (complexity > threshold){ - String message = String.format(MESSAGE, complexity, threshold); - ASTNode nameNode = function.getNameNode(); - if (nameNode != null) { - PreciseIssue issue = ctx.addIssue(nameNode.getPsi(), message) - .withCost(complexity - threshold); - secondaryLocations.forEach(issue::secondary); - } - } - }); + public Set subscribedKinds() { + return immutableSet(PythonGrammar.FUNCDEF); + } + + @Override + public void visitNode(AstNode astNode) { + if (astNode.hasAncestor(PythonGrammar.FUNCDEF)) { + return; + } + List secondaryLocations = new ArrayList<>(); + int complexity = CognitiveComplexityVisitor.complexity(astNode, (node, message) -> secondaryLocations.add(IssueLocation.preciseLocation(node, message))); + if (complexity > threshold){ + String message = String.format(MESSAGE, complexity, threshold); + PreciseIssue issue = addIssue(astNode.getFirstChild(PythonGrammar.FUNCNAME), message) + .withCost(complexity - threshold); + secondaryLocations.forEach(issue::secondary); + } } } 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 216ca61f2b..5dcc4fbca6 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,53 +19,54 @@ */ package org.sonar.python.checks; -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 com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.List; +import java.util.Set; 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 = "S1066") +@Rule(key = CollapsibleIfStatementsCheck.CHECK_KEY) 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 void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.IF_STATEMENT, ctx -> { - PyIfStatement ifStatement = (PyIfStatement) ctx.syntaxNode(); - - if (ifStatement.getElsePart() != null) { - return; - } + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.IF_STMT); + } - PyIfPart[] elifParts = ifStatement.getElifParts(); - PyIfPart lastIfPart = elifParts.length == 0 ? ifStatement.getIfPart() : elifParts[elifParts.length - 1]; + @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"); + } + } - 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 boolean hasElseOrElif(AstNode ifNode) { + return ifNode.hasDirectChildren(PythonKeyword.ELIF) || ifNode.hasDirectChildren(PythonKeyword.ELSE); } - @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; + 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); } } return null; } - - private static PsiElement ifKeyword(PyIfStatement ifStatement) { - return ifStatement.getNode().findLeafElementAt(0).getPsi(); - } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/CommentRegularExpressionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/CommentRegularExpressionCheck.java index 9a38370f02..88b5eadb11 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/CommentRegularExpressionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/CommentRegularExpressionCheck.java @@ -19,14 +19,16 @@ */ package org.sonar.python.checks; -import com.jetbrains.python.PyTokenTypes; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.python.PythonCheck; -@Rule(key = "CommentRegularExpression") +@Rule(key = CommentRegularExpressionCheck.CHECK_KEY) public class CommentRegularExpressionCheck extends PythonCheck { + public static final String CHECK_KEY = "CommentRegularExpression"; private static final String DEFAULT_REGULAR_EXPRESSION = ""; private static final String DEFAULT_MESSAGE = "The regular expression matches this comment"; @@ -59,12 +61,14 @@ private Pattern pattern() { } @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyTokenTypes.END_OF_LINE_COMMENT, ctx -> { - if (pattern().matcher(ctx.syntaxNode().getText()).matches()) { - ctx.addIssue(ctx.syntaxNode(), message); + public void visitToken(Token token) { + if (pattern() != null) { + for (Trivia trivia : token.getTrivia()) { + if (trivia.isComment() && pattern().matcher(trivia.getToken().getOriginalValue()).matches()) { + addIssue(trivia.getToken(), message); + } } - }); + } } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/EmptyNestedBlockCheck.java b/python-checks/src/main/java/org/sonar/python/checks/EmptyNestedBlockCheck.java index 7d708fd2ba..084c3eb0b7 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/EmptyNestedBlockCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/EmptyNestedBlockCheck.java @@ -19,47 +19,77 @@ */ package org.sonar.python.checks; -import com.intellij.psi.PsiComment; -import com.intellij.psi.PsiElement; -import com.intellij.psi.SyntaxTraverser; -import com.intellij.psi.tree.IElementType; -import com.intellij.util.containers.JBIterable; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyStatement; -import com.jetbrains.python.psi.PyStatementList; -import java.util.Arrays; -import java.util.Optional; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; +import java.util.Collections; +import java.util.Set; +import java.util.function.Predicate; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonGrammar; +import org.sonar.sslr.ast.AstSelect; -@Rule(key = "S108") +@Rule(key = EmptyNestedBlockCheck.CHECK_KEY) public class EmptyNestedBlockCheck extends PythonCheck { + public static final String CHECK_KEY = "S108"; + private static final Predicate NOT_PASS_PREDICATE = new NotPassPredicate(); + private static final String MESSAGE = "Either remove or fill this block of code."; @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.STATEMENT_LIST, ctx -> { - PyStatementList statementList = (PyStatementList) ctx.syntaxNode(); + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.SUITE); + } + + @Override + public void visitNode(AstNode suiteNode) { + if (suiteNode.getParent().is(PythonGrammar.FUNCDEF, PythonGrammar.CLASSDEF) || isInExcept(suiteNode)) { + return; + } - PsiElement parent = statementList.getParent(); - IElementType parentType = parent.getNode().getElementType(); - if (parentType == PyElementTypes.FUNCTION_DECLARATION - || parentType == PyElementTypes.CLASS_DECLARATION - || parentType == PyElementTypes.EXCEPT_PART) { + AstSelect suite = suiteNode.select(); + AstSelect stmtLists = suite.children(PythonGrammar.STMT_LIST); + if (stmtLists.isEmpty()) { + AstSelect statementSelect = suite.children(PythonGrammar.STATEMENT); + if (statementSelect.children(PythonGrammar.COMPOUND_STMT).isNotEmpty()) { return; } + stmtLists = statementSelect.children(PythonGrammar.STMT_LIST); + } + + AstSelect nonPassSimpleStatements = stmtLists + .children(PythonGrammar.SIMPLE_STMT) + .children() + .filter(NOT_PASS_PREDICATE); + if (nonPassSimpleStatements.isEmpty() && !containsComment(suiteNode)) { + addIssue(stmtLists.get(0), MESSAGE); + } + } - Optional nonPassStatement = Arrays.stream(statementList.getStatements()) - .filter(s -> s.getNode().getElementType() != PyElementTypes.PASS_STATEMENT) - .findFirst(); - if (!nonPassStatement.isPresent() && !containsComment(statementList)) { - ctx.addIssue(statementList, "Either remove or fill this block of code."); + private static boolean isInExcept(AstNode suiteNode) { + return suiteNode.getParent().is(PythonGrammar.TRY_STMT) + && suiteNode.getPreviousSibling().getPreviousSibling().is(PythonGrammar.EXCEPT_CLAUSE); + } + + private static boolean containsComment(AstNode suiteNode) { + for (Token token : suiteNode.getTokens()) { + for (Trivia trivia : token.getTrivia()) { + if (trivia.isComment()) { + return true; + } } - }); + } + return false; } - private static boolean containsComment(PyStatementList statementList) { - JBIterable comments = SyntaxTraverser.psiTraverser(statementList.getParent()).traverse().filter(PsiComment.class); - return !comments.isEmpty(); + private static class NotPassPredicate implements Predicate { + + @Override + public boolean test(AstNode node) { + return !node.getType().equals(PythonGrammar.PASS_STMT); + } + } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/ExecStatementUsageCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ExecStatementUsageCheck.java index 0c10c4addc..f45452de54 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ExecStatementUsageCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ExecStatementUsageCheck.java @@ -19,26 +19,25 @@ */ package org.sonar.python.checks; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyParenthesizedExpression; -import java.util.List; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonGrammar; -@Rule(key = "ExecStatementUsage") +@Rule(key = ExecStatementUsageCheck.CHECK_KEY) public class ExecStatementUsageCheck extends PythonCheck { + public static final String CHECK_KEY = "ExecStatementUsage"; + @Override + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.EXEC_STMT); + } @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.EXEC_STATEMENT, ctx -> { - List expressions = PsiTreeUtil.getChildrenOfTypeAsList(ctx.syntaxNode(), PyExpression.class); - if (expressions.size() == 1 && expressions.get(0) instanceof PyParenthesizedExpression) { - return; - } - ctx.addIssue(ctx.syntaxNode().getFirstChild(), "Do not use exec statement."); - }); + public void visitNode(AstNode astNode) { + addIssue(astNode.getFirstChild(), "Do not use exec statement."); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/ExitHasBadArgumentsCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ExitHasBadArgumentsCheck.java index b8c2355af5..9df49c4559 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ExitHasBadArgumentsCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ExitHasBadArgumentsCheck.java @@ -19,45 +19,61 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.PyParameterList; +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 org.sonar.check.Rule; import org.sonar.python.IssueLocation; import org.sonar.python.PythonCheck; -import org.sonar.python.SubscriptionContext; +import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.PythonPunctuator; -@Rule(key = "S2733") +@Rule(key = ExitHasBadArgumentsCheck.CHECK_KEY) public class ExitHasBadArgumentsCheck extends PythonCheck { + public static final String MESSAGE_ADD = "Add the missing argument."; + public static final String MESSAGE_REMOVE = "Remove the unnecessary argument."; + private static final int EXIT_ARGUMENTS_NUMBER = 4; - @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.FUNCTION_DECLARATION, ctx -> { - PyFunction function = (PyFunction) ctx.syntaxNode(); - ASTNode nameNode = function.getNameNode(); - if (nameNode == null || !"__exit__".equals(nameNode.getText())) { - return; - } + public static final String CHECK_KEY = "S2733"; - PyParameterList parameters = function.getParameterList(); - if (parameters.hasPositionalContainer() || parameters.hasKeywordContainer()) { - return; - } + @Override + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.FUNCDEF); + } - int numberOfParameters = parameters.getParameters().length; - if (numberOfParameters < EXIT_ARGUMENTS_NUMBER) { - raiseIssue(ctx, nameNode, parameters, "Add the missing argument."); - } else if (numberOfParameters > EXIT_ARGUMENTS_NUMBER) { - raiseIssue(ctx, nameNode, parameters, "Remove the unnecessary argument."); + @Override + public void visitNode(AstNode node) { + if (!"__exit__".equals(node.getFirstChild(PythonGrammar.FUNCNAME).getToken().getValue())){ + return; + } + AstNode varArgList = node.getFirstChild(PythonGrammar.TYPEDARGSLIST); + if (varArgList != null) { + List arguments = varArgList.getChildren(PythonGrammar.TFPDEF); + for (AstNode argument : arguments) { + if (argument.getPreviousSibling() != null && argument.getPreviousSibling().is(PythonPunctuator.MUL_MUL, PythonPunctuator.MUL)) { + return; + } } - }); + raiseIssue(node, arguments.size()); + } else { + raiseIssue(node, 0); + } } - private static void raiseIssue(SubscriptionContext ctx, ASTNode nameNode, PyParameterList parameterList, String message) { - ctx.addIssue(IssueLocation.preciseLocation(nameNode.getPsi(), parameterList, message)); + private void raiseIssue(AstNode node, int argumentsNumber) { + if (argumentsNumber != EXIT_ARGUMENTS_NUMBER){ + String message = MESSAGE_ADD; + if (argumentsNumber > EXIT_ARGUMENTS_NUMBER){ + message = MESSAGE_REMOVE; + } + AstNode funcName = node.getFirstChild(PythonGrammar.FUNCNAME); + AstNode rightParenthesis = node.getFirstChild(PythonPunctuator.RPARENTHESIS); + addIssue(IssueLocation.preciseLocation(funcName, rightParenthesis, message)); + } } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/FileComplexityCheck.java b/python-checks/src/main/java/org/sonar/python/checks/FileComplexityCheck.java index ae4a5a3d38..0f43ce88fc 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/FileComplexityCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/FileComplexityCheck.java @@ -19,7 +19,8 @@ */ package org.sonar.python.checks; -import com.jetbrains.python.psi.PyFileElementType; +import com.sonar.sslr.api.AstNode; +import java.text.MessageFormat; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.python.PythonCheck; @@ -35,18 +36,16 @@ public class FileComplexityCheck extends PythonCheck { int maximumFileComplexityThreshold = DEFAULT_MAXIMUM_FILE_COMPLEXITY_THRESHOLD; @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyFileElementType.INSTANCE, ctx -> { - int complexity = ComplexityVisitor.complexity(ctx.syntaxNode()); - if (complexity > maximumFileComplexityThreshold) { - String message = String.format( - "File has a complexity of %s which is greater than %s authorized.", - complexity, - maximumFileComplexityThreshold); - ctx.addFileIssue(message).withCost(complexity - maximumFileComplexityThreshold); - } - }); + public void leaveFile(AstNode astNode) { + int complexity = ComplexityVisitor.complexity(astNode); + if (complexity > maximumFileComplexityThreshold) { + String message = MessageFormat.format( + "File has a complexity of {0,number,integer} which is greater than {1,number,integer} authorized.", + complexity, + maximumFileComplexityThreshold); + addFileIssue(message) + .withCost(complexity - maximumFileComplexityThreshold); + } } - } diff --git a/python-checks/src/main/java/org/sonar/python/checks/FixmeCommentCheck.java b/python-checks/src/main/java/org/sonar/python/checks/FixmeCommentCheck.java index da2180e063..75a6fa51c3 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/FixmeCommentCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/FixmeCommentCheck.java @@ -19,27 +19,36 @@ */ package org.sonar.python.checks; -import com.intellij.psi.PsiElement; -import com.jetbrains.python.PyTokenTypes; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; -@Rule(key = "S1134") +@Rule(key = FixmeCommentCheck.CHECK_KEY) public class FixmeCommentCheck extends PythonCheck { + + public static final String CHECK_KEY = "S1134"; + private static final String FIXME_COMMENT_PATTERN = "^#[ ]*fixme.*"; + private static final String MESSAGE = "Take the required action to fix the issue indicated by this \"FIXME\" comment."; + + private Pattern pattern; @Override - public void initialize(Context context) { - final Pattern pattern = Pattern.compile(FIXME_COMMENT_PATTERN, Pattern.CASE_INSENSITIVE); - context.registerSyntaxNodeConsumer(PyTokenTypes.END_OF_LINE_COMMENT, ctx -> { - PsiElement node = ctx.syntaxNode(); - String comment = node.getText(); + public void visitFile(AstNode astNode) { + pattern = Pattern.compile(FIXME_COMMENT_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public void visitToken(Token token) { + for (Trivia trivia : token.getTrivia()) { + String comment = trivia.getToken().getValue(); if (pattern.matcher(comment).matches()) { - ctx.addIssue(node, "Take the required action to fix the issue indicated by this \"FIXME\" comment."); + addIssue(trivia.getToken(), MESSAGE); } - }); + } } - } diff --git a/python-checks/src/main/java/org/sonar/python/checks/FunctionComplexityCheck.java b/python-checks/src/main/java/org/sonar/python/checks/FunctionComplexityCheck.java index fd68bd8bd7..071eb5f2aa 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/FunctionComplexityCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/FunctionComplexityCheck.java @@ -19,12 +19,14 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyFunction; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonGrammar; import org.sonar.python.metrics.ComplexityVisitor; @Rule(key = "FunctionComplexity") @@ -38,18 +40,18 @@ public class FunctionComplexityCheck extends PythonCheck { int maximumFunctionComplexityThreshold = DEFAULT_MAXIMUM_FUNCTION_COMPLEXITY_THRESHOLD; @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.FUNCTION_DECLARATION, ctx -> { - PyFunction node = (PyFunction) ctx.syntaxNode(); - int complexity = ComplexityVisitor.complexity(node); - if (complexity > maximumFunctionComplexityThreshold) { - String message = String.format(MESSAGE, complexity, maximumFunctionComplexityThreshold); - ASTNode nameNode = node.getNameNode(); - if (nameNode != null) { - ctx.addIssue(nameNode.getPsi(), message).withCost(complexity - maximumFunctionComplexityThreshold); - } - } - }); + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.FUNCDEF); + } + + @Override + public void visitNode(AstNode node) { + int complexity = ComplexityVisitor.complexity(node); + if (complexity > maximumFunctionComplexityThreshold) { + String message = String.format(MESSAGE, complexity, maximumFunctionComplexityThreshold); + addIssue(node.getFirstChild(PythonGrammar.FUNCNAME), message) + .withCost(complexity - maximumFunctionComplexityThreshold); + } } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/FunctionNameCheck.java b/python-checks/src/main/java/org/sonar/python/checks/FunctionNameCheck.java index 9274fc9535..1e0fa6fc83 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/FunctionNameCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/FunctionNameCheck.java @@ -19,11 +19,12 @@ */ package org.sonar.python.checks; -import com.jetbrains.python.psi.PyFunction; +import com.sonar.sslr.api.AstNode; import org.sonar.check.Rule; -@Rule(key = "S1542") +@Rule(key = FunctionNameCheck.CHECK_KEY) public class FunctionNameCheck extends AbstractFunctionNameCheck { + public static final String CHECK_KEY = "S1542"; @Override public String typeName() { @@ -31,8 +32,8 @@ public String typeName() { } @Override - public boolean shouldCheckFunctionDeclaration(PyFunction function) { - return !CheckUtils.isMethodDefinition(function); + public boolean shouldCheckFunctionDeclaration(AstNode astNode) { + return !CheckUtils.isMethodDefinition(astNode); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java index 2ce56c48fd..f6935b9351 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java @@ -19,21 +19,22 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.intellij.psi.PsiElement; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyStringLiteralExpression; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; -import org.sonar.python.SubscriptionContext; +import org.sonar.python.api.PythonTokenType; -@Rule(key = "S1313") +@Rule(key = HardcodedIPCheck.CHECK_KEY) public class HardcodedIPCheck extends PythonCheck { + public static final String CHECK_KEY = "S1313"; private static final String IPV4_ALONE = "(?(?:\\d{1,3}\\.){3}\\d{1,3})"; @@ -53,17 +54,13 @@ public class HardcodedIPCheck extends PythonCheck { String message = "Make sure using this hardcoded IP address \"%s\" is safe here."; @Override - 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()); - } - }); + public Set subscribedKinds() { + return Collections.singleton(PythonTokenType.STRING); } - private void checkLiteral(SubscriptionContext ctx, PsiElement literal) { - String value = literal.getNode().getText(); + @Override + public void visitNode(AstNode node) { + String value = node.getTokenOriginalValue(); if (value.length() <= 2 || isMultilineString(value)) { return; } @@ -72,7 +69,7 @@ private void checkLiteral(SubscriptionContext ctx, PsiElement literal) { if (matcher.matches()) { String ip = matcher.group("ipv4"); if (isValidIPV4(ip) && !isIPV4Exception(ip)) { - ctx.addIssue(literal, String.format(message, ip)); + addIssue(node, String.format(message, ip)); } } else { IPV6_REGEX_LIST.stream() @@ -84,7 +81,7 @@ private void checkLiteral(SubscriptionContext ctx, PsiElement literal) { String ipv4 = match.group("ipv4"); return isValidIPV6(ipv6, ipv4) && !isIPV6Exception(ipv6) ? ipv6 : null; }) - .ifPresent(ipv6 -> ctx.addIssue(literal, String.format(message, ipv6))); + .ifPresent(ipv6 -> addIssue(node, String.format(message, ipv6))); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/InequalityUsageCheck.java b/python-checks/src/main/java/org/sonar/python/checks/InequalityUsageCheck.java index 33762dcfbc..b2cc109e34 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/InequalityUsageCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/InequalityUsageCheck.java @@ -19,22 +19,27 @@ */ package org.sonar.python.checks; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyBinaryExpression; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonPunctuator; -@Rule(key = "InequalityUsage") +@Rule(key = InequalityUsageCheck.CHECK_KEY) public class InequalityUsageCheck extends PythonCheck { + public static final String CHECK_KEY = "InequalityUsage"; + + @Override + public Set subscribedKinds() { + return Collections.singleton(PythonPunctuator.NOT_EQU2); + } + @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.BINARY_EXPRESSION, ctx -> { - PyBinaryExpression node = (PyBinaryExpression) ctx.syntaxNode(); - if ("<>".equals(node.getPsiOperator().getText())) { - ctx.addIssue(node.getPsiOperator(), "Replace \"<>\" by \"!=\"."); - } - }); + public void visitNode(AstNode astNode) { + addIssue(astNode, "Replace \"<>\" by \"!=\"."); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/LongIntegerWithLowercaseSuffixUsageCheck.java b/python-checks/src/main/java/org/sonar/python/checks/LongIntegerWithLowercaseSuffixUsageCheck.java index b77c04a46a..c55becbdbc 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/LongIntegerWithLowercaseSuffixUsageCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/LongIntegerWithLowercaseSuffixUsageCheck.java @@ -19,23 +19,31 @@ */ package org.sonar.python.checks; -import com.intellij.psi.PsiElement; -import com.jetbrains.python.PyElementTypes; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonTokenType; -@Rule(key = "LongIntegerWithLowercaseSuffixUsage") +@Rule(key = LongIntegerWithLowercaseSuffixUsageCheck.CHECK_KEY) public class LongIntegerWithLowercaseSuffixUsageCheck extends PythonCheck { + public static final String CHECK_KEY = "LongIntegerWithLowercaseSuffixUsage"; + private static final String MESSAGE = "Replace suffix in long integers from lower case \"l\" to upper case \"L\"."; + + @Override + public Set subscribedKinds() { + return Collections.singleton(PythonTokenType.NUMBER); + } + @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.INTEGER_LITERAL_EXPRESSION, ctx -> { - PsiElement node = ctx.syntaxNode(); - String value = node.getText(); - if (value.charAt(value.length() - 1) == 'l') { - ctx.addIssue(node, "Replace suffix in long integers from lower case \"l\" to upper case \"L\"."); - } - }); + public void visitNode(AstNode astNode) { + String value = astNode.getTokenValue(); + if (value.charAt(value.length() - 1) == 'l') { + addIssue(astNode, MESSAGE); + } } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/MethodNameCheck.java b/python-checks/src/main/java/org/sonar/python/checks/MethodNameCheck.java index 8ea573df29..901c5395c8 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/MethodNameCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/MethodNameCheck.java @@ -19,19 +19,20 @@ */ package org.sonar.python.checks; -import com.jetbrains.python.psi.PyFunction; +import com.sonar.sslr.api.AstNode; import org.sonar.check.Rule; -@Rule(key = "S100") +@Rule(key = MethodNameCheck.CHECK_KEY) public class MethodNameCheck extends AbstractFunctionNameCheck { + public static final String CHECK_KEY = "S100"; @Override public String typeName() { return "method"; } @Override - public boolean shouldCheckFunctionDeclaration(PyFunction function) { - return CheckUtils.isMethodOfNonDerivedClass(function); + public boolean shouldCheckFunctionDeclaration(AstNode astNode) { + return CheckUtils.isMethodOfNonDerivedClass(astNode); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/NoPersonReferenceInTodoCheck.java b/python-checks/src/main/java/org/sonar/python/checks/NoPersonReferenceInTodoCheck.java index 1acfc870ac..02b3c51266 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/NoPersonReferenceInTodoCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/NoPersonReferenceInTodoCheck.java @@ -19,18 +19,25 @@ */ package org.sonar.python.checks; -import com.intellij.psi.PsiElement; -import com.jetbrains.python.PyTokenTypes; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.python.PythonCheck; -@Rule(key = "S1707") +@Rule(key = NoPersonReferenceInTodoCheck.CHECK_KEY) public class NoPersonReferenceInTodoCheck extends PythonCheck { + + public static final String CHECK_KEY = "S1707"; + public static final String MESSAGE = "Add a citation of the person who can best explain this comment."; + private static final String DEFAULT_PERSON_REFERENCE_PATTERN = "[ ]*\\([ _a-zA-Z0-9@.]+\\)"; private static final String COMMENT_PATTERN = "^#[ ]*(todo|fixme)"; + private Pattern patternTodoFixme; + private Pattern patternPersonReference; @RuleProperty( key = "pattern", @@ -38,21 +45,29 @@ public class NoPersonReferenceInTodoCheck extends PythonCheck { public String personReferencePatternString = DEFAULT_PERSON_REFERENCE_PATTERN; @Override - public void initialize(Context context) { - final Pattern patternTodoFixme = Pattern.compile(COMMENT_PATTERN, Pattern.CASE_INSENSITIVE); - final Pattern patternPersonReference = Pattern.compile(personReferencePatternString); - context.registerSyntaxNodeConsumer(PyTokenTypes.END_OF_LINE_COMMENT, ctx -> { - PsiElement node = ctx.syntaxNode(); - String comment = node.getText(); - Matcher matcher = patternTodoFixme.matcher(comment); - if (matcher.find()) { - String tail = comment.substring(matcher.end()); - if (!patternPersonReference.matcher(tail).find()) { - ctx.addIssue(node, "Add a citation of the person who can best explain this comment."); - } + public void visitFile(AstNode astNode) { + patternTodoFixme = Pattern.compile(COMMENT_PATTERN, Pattern.CASE_INSENSITIVE); + patternPersonReference = Pattern.compile(personReferencePatternString); + } + + @Override + public void visitToken(Token token) { + for (Trivia trivia : token.getTrivia()) { + if (trivia.isComment()) { + visitComment(trivia); } - }); + } } + private void visitComment(Trivia trivia) { + String comment = trivia.getToken().getValue(); + Matcher matcher = patternTodoFixme.matcher(comment); + if (matcher.find()) { + String tail = comment.substring(matcher.end()); + if (!patternPersonReference.matcher(tail).find()) { + addIssue(trivia.getToken(), MESSAGE); + } + } + } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/PrintStatementUsageCheck.java b/python-checks/src/main/java/org/sonar/python/checks/PrintStatementUsageCheck.java index 933929d012..ec555eadc0 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/PrintStatementUsageCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/PrintStatementUsageCheck.java @@ -19,54 +19,26 @@ */ package org.sonar.python.checks; -import com.intellij.lang.ASTNode; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyBinaryExpression; -import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyExpressionStatement; -import com.jetbrains.python.psi.PyParenthesizedExpression; -import com.jetbrains.python.psi.PyPrintTarget; -import java.util.List; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonCheck; +import org.sonar.python.api.PythonGrammar; -@Rule(key = "PrintStatementUsage") +@Rule(key = PrintStatementUsageCheck.CHECK_KEY) public class PrintStatementUsageCheck extends PythonCheck { - - public static final String MESSAGE = "Replace print statement by built-in function."; + public static final String CHECK_KEY = "PrintStatementUsage"; @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyElementTypes.PRINT_STATEMENT, ctx -> { - List expressions = PsiTreeUtil.getChildrenOfAnyType(ctx.syntaxNode(), PyExpression.class, PyPrintTarget.class); - if (expressions.size() == 1 && expressions.get(0) instanceof PyParenthesizedExpression) { - return; - } - ctx.addIssue(ctx.syntaxNode().getFirstChild(), MESSAGE); - }); - - // We should raise issues on "print >>file, str" which is syntactically valid in Python 3 - context.registerSyntaxNodeConsumer(PyElementTypes.BINARY_EXPRESSION, ctx -> { - PyBinaryExpression binary = (PyBinaryExpression) ctx.syntaxNode(); - IElementType parentType = binary.getParent().getNode().getElementType(); - if (parentType != PyElementTypes.EXPRESSION_STATEMENT && parentType != PyElementTypes.TUPLE_EXPRESSION) { - return; - } - if (binary.isOperator(">>") && "print".equals(binary.getLeftExpression().getNode().getText())) { - ctx.addIssue(binary.getLeftExpression(), MESSAGE); - } - }); + public Set subscribedKinds() { + return Collections.singleton(PythonGrammar.PRINT_STMT); + } - context.registerSyntaxNodeConsumer(PyElementTypes.EXPRESSION_STATEMENT, ctx -> { - PyExpressionStatement statement = (PyExpressionStatement) ctx.syntaxNode(); - ASTNode expressionNode = statement.getExpression().getNode(); - if (expressionNode.getElementType() == PyElementTypes.REFERENCE_EXPRESSION && "print".equals(expressionNode.getText())) { - ctx.addIssue(statement, MESSAGE); - } - }); + @Override + public void visitNode(AstNode astNode) { + addIssue(astNode.getFirstChild(), "Replace print statement by built-in function."); } } diff --git a/python-checks/src/test/java/org/sonar/python/checks/PrintStatementUsageCheckTest.java b/python-checks/src/test/java/org/sonar/python/checks/PrintStatementUsageCheckTest.java index 6c5cbd825a..a890fceee1 100644 --- a/python-checks/src/test/java/org/sonar/python/checks/PrintStatementUsageCheckTest.java +++ b/python-checks/src/test/java/org/sonar/python/checks/PrintStatementUsageCheckTest.java @@ -27,7 +27,6 @@ public class PrintStatementUsageCheckTest { @Test public void test() { PythonCheckVerifier.verify("src/test/resources/checks/printStatementUsage.py", new PrintStatementUsageCheck()); - PythonCheckVerifier.verify("src/test/resources/checks/printStatementUsage-validpython3.py", new PrintStatementUsageCheck()); } } diff --git a/python-checks/src/test/java/org/sonar/python/checks/utils/PythonCheckVerifier.java b/python-checks/src/test/java/org/sonar/python/checks/utils/PythonCheckVerifier.java index 84ed4563e8..4258c6641a 100644 --- a/python-checks/src/test/java/org/sonar/python/checks/utils/PythonCheckVerifier.java +++ b/python-checks/src/test/java/org/sonar/python/checks/utils/PythonCheckVerifier.java @@ -26,7 +26,6 @@ import com.sonar.sslr.api.Trivia; import java.io.File; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import org.sonar.python.IssueLocation; @@ -34,9 +33,7 @@ import org.sonar.python.PythonCheck.PreciseIssue; import org.sonar.python.PythonVisitor; import org.sonar.python.PythonVisitorContext; -import org.sonar.python.SubscriptionVisitor; import org.sonar.python.TestPythonVisitorRunner; -import org.sonar.python.frontend.PythonParser; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; @@ -47,7 +44,6 @@ public class PythonCheckVerifier extends PythonVisitor { public static List scanFileForIssues(File file, PythonCheck check) { PythonVisitorContext context = TestPythonVisitorRunner.createContext(file); - SubscriptionVisitor.analyze(Collections.singletonList(check), context, PythonParser.parse(file)); check.scanFile(context); return context.getIssues(); } diff --git a/python-checks/src/test/resources/checks/afterJumpStatement.py b/python-checks/src/test/resources/checks/afterJumpStatement.py index f621b40546..4bb42c1d60 100644 --- a/python-checks/src/test/resources/checks/afterJumpStatement.py +++ b/python-checks/src/test/resources/checks/afterJumpStatement.py @@ -2,17 +2,17 @@ break for i in range(1): - break; print(i) # Noncompliant {{Refactor this piece of code to not have any dead code after this "break".}} + break; print(i) # Noncompliant {{Remove the code after this "break".}} # ^^^^^ for i in range(1): - continue # Noncompliant {{Refactor this piece of code to not have any dead code after this "continue".}} + continue # Noncompliant {{Remove the code after this "continue".}} print(i) if True: print(1) - raise TypeError("message") # Noncompliant {{Refactor this piece of code to not have any dead code after this "raise".}} -# ^^^^^ + raise TypeError("message") # Noncompliant {{Remove the code after this "raise".}} +# ^^^^^^^^^^^^^^^^^^^^^^^^^^ if True: pass def fun1(): @@ -23,16 +23,11 @@ def fun2(): return 2 def fun3(): - return 2 # Noncompliant {{Refactor this piece of code to not have any dead code after this "return".}} + return 2 # Noncompliant {{Remove the code after this "return".}} print(1) def fun4(): return 2; def fun5(): - return 2; print(1) # Noncompliant {{Refactor this piece of code to not have any dead code after this "return".}} - -x = 42 -raise TypeError("message") # Noncompliant -print x - + return 2; print(1) # Noncompliant {{Remove the code after this "return".}} diff --git a/python-checks/src/test/resources/checks/backslashInString.py b/python-checks/src/test/resources/checks/backslashInString.py index 6becbaee81..1f6949030e 100644 --- a/python-checks/src/test/resources/checks/backslashInString.py +++ b/python-checks/src/test/resources/checks/backslashInString.py @@ -24,7 +24,3 @@ z = "*a*\ s" # Noncompliant z = """\ s""" # Noncompliant z = "" -re.compile(r'...' - r'\("abc ' - 'def"\)$') # Noncompliant - diff --git a/python-checks/src/test/resources/checks/backticksUsage.py b/python-checks/src/test/resources/checks/backticksUsage.py index 07389fa001..8e9266d753 100644 --- a/python-checks/src/test/resources/checks/backticksUsage.py +++ b/python-checks/src/test/resources/checks/backticksUsage.py @@ -2,4 +2,3 @@ def foo(): `num` # Noncompliant {{Use "repr" instead.}} # ^^^^^ foo() - repr("a") diff --git a/python-checks/src/test/resources/checks/emptyNestedBlock.py b/python-checks/src/test/resources/checks/emptyNestedBlock.py index f843949f40..11356dd4bb 100644 --- a/python-checks/src/test/resources/checks/emptyNestedBlock.py +++ b/python-checks/src/test/resources/checks/emptyNestedBlock.py @@ -28,9 +28,6 @@ except: pass -if 3 > 2: # nothing to do - pass - if 3 > 2: # nothing to do pass @@ -38,12 +35,6 @@ if 3 > 2: pass # nothing to do -# Noncompliant@+2 -if 3 > 2: - pass -# just a comment -elif condition: - foo() def empty_function(): pass diff --git a/python-checks/src/test/resources/checks/execStatementUsage.py b/python-checks/src/test/resources/checks/execStatementUsage.py index 9fc270c651..d07cdcc2a8 100644 --- a/python-checks/src/test/resources/checks/execStatementUsage.py +++ b/python-checks/src/test/resources/checks/execStatementUsage.py @@ -1,10 +1,4 @@ -def getS(): - return "%s" - def foo(): exec 'print 1' # Noncompliant {{Do not use exec statement.}} # ^^^^ exec('print 1') - exec ('print 1') - exec "%s %s" % ("a", "b") # Noncompliant {{Do not use exec statement.}} - exec getS() % "a" # Noncompliant {{Do not use exec statement.}} diff --git a/python-checks/src/test/resources/checks/exitHasBadArguments.py b/python-checks/src/test/resources/checks/exitHasBadArguments.py index 0c6b55a7a0..a3b6959083 100644 --- a/python-checks/src/test/resources/checks/exitHasBadArguments.py +++ b/python-checks/src/test/resources/checks/exitHasBadArguments.py @@ -36,12 +36,6 @@ def __exit__(self, *args, **args2): pass class MyClass6: - def __enter__(self): - pass - def __exit__(self, **args2): - pass - -class MyClass7: def __enter__(self): pass def __exit__(): # Noncompliant diff --git a/python-checks/src/test/resources/checks/functionName.py b/python-checks/src/test/resources/checks/functionName.py index bd837097ea..24d86fa0fa 100644 --- a/python-checks/src/test/resources/checks/functionName.py +++ b/python-checks/src/test/resources/checks/functionName.py @@ -14,8 +14,3 @@ def long_function_name_is_still_correct(): class MyClass: def This_Is_A_Method(): pass - -class MyClass2: - if 1: - def Badly_Named_Function(self): - pass diff --git a/python-checks/src/test/resources/checks/methodName.py b/python-checks/src/test/resources/checks/methodName.py index 3ae8f2b5c3..70405f9d84 100644 --- a/python-checks/src/test/resources/checks/methodName.py +++ b/python-checks/src/test/resources/checks/methodName.py @@ -15,20 +15,3 @@ def setUp(self): # ok, potentially overridden method, the name can't be changed def This_Is_A_Function(): pass - -class MyClass2: - if 1: - def Badly_Named_Function(self): # Noncompliant {{Rename method "Badly_Named_Function" to match the regular expression ^[a-z_][a-z0-9_]{2,}$.}} - pass - -class MyClass3(object): - def correct_method_name(): - pass - - def Incorrect_Method_Name(): # Noncompliant {{Rename method "Incorrect_Method_Name" to match the regular expression ^[a-z_][a-z0-9_]{2,}$.}} -# ^^^^^^^^^^^^^^^^^^^^^ - pass - -class DoubleInheritance(MyClass, MyClass3): - def another(self): - pass diff --git a/python-checks/src/test/resources/checks/printStatementUsage-validpython3.py b/python-checks/src/test/resources/checks/printStatementUsage-validpython3.py deleted file mode 100644 index c55d181b0f..0000000000 --- a/python-checks/src/test/resources/checks/printStatementUsage-validpython3.py +++ /dev/null @@ -1,14 +0,0 @@ -import sys -if (2 > 3): - print >>sys.stderr, ("hello") #Noncompliant -# ^^^^^ - print + sys.stderr, ("hello") - abcde >>sys.stderr, ("hello") - print("hello", file=sys.stderr) - - print >> f #Noncompliant - x = print >> f - - print #Noncompliant - print() - myprint diff --git a/python-checks/src/test/resources/checks/printStatementUsage.py b/python-checks/src/test/resources/checks/printStatementUsage.py index 731b627627..4f34096848 100644 --- a/python-checks/src/test/resources/checks/printStatementUsage.py +++ b/python-checks/src/test/resources/checks/printStatementUsage.py @@ -1,17 +1,4 @@ -import sys - -def getS(): - return "%s" - def foo(): print 1 # Noncompliant {{Replace print statement by built-in function.}} # ^^^^^ print('1') - print ('1') - print "%s %s" % ("a", "b") # Noncompliant {{Replace print statement by built-in function.}} - print getS() % "a" # Noncompliant {{Replace print statement by built-in function.}} - -print >>sys.stderr, ("fatal error") # Noncompliant -print >>sys.stderr, "fatal error" # Noncompliant - -print # Noncompliant {{Replace print statement by built-in function.}} diff --git a/python-frontend/pom.xml b/python-frontend/pom.xml deleted file mode 100644 index e97c8311f8..0000000000 --- a/python-frontend/pom.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - 4.0.0 - - - org.sonarsource.python - python - 1.15-SNAPSHOT - - - python-frontend - - Python :: Frontend - - - - org.sonarsource.sslr - sslr-core - - - com.jetbrains.pycharm - extensions - ${pycharm.version} - - - com.jetbrains.pycharm - platform-api - ${pycharm.version} - - - com.jetbrains.pycharm - platform-impl - ${pycharm.version} - - - com.jetbrains.pycharm - pycharm - ${pycharm.version} - - - com.jetbrains.pycharm - pycharm-pydev - ${pycharm.version} - - - com.jetbrains.pycharm - resources_en - ${pycharm.version} - - - com.jetbrains.pycharm - util - ${pycharm.version} - - - - - com.google.guava - guava - 25.1-jre - - - org.jetbrains.kotlin - kotlin-stdlib - 1.2.50 - - - org.jetbrains.kotlin - kotlin-stdlib-common - - - - - org.picocontainer - picocontainer - 1.2 - - - org.jetbrains.intellij.deps - trove4j - 1.0.20190514 - - - - junit - junit - - - org.assertj - assertj-core - - - org.mockito - mockito-core - - - - diff --git a/python-frontend/src/main/java/org/sonar/python/frontend/PythonKeyword.java b/python-frontend/src/main/java/org/sonar/python/frontend/PythonKeyword.java deleted file mode 100644 index 6109c31a07..0000000000 --- a/python-frontend/src/main/java/org/sonar/python/frontend/PythonKeyword.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python.frontend; - -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.PyElementType; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -public class PythonKeyword { - - private PythonKeyword() { - // empty constructor - } - - private static final Set KEYWORDS_ELEMENT_TYPES = new HashSet<>(Arrays.asList( - PyTokenTypes.AND_KEYWORD, - PyTokenTypes.AS_KEYWORD, - PyTokenTypes.ASSERT_KEYWORD, - PyTokenTypes.BREAK_KEYWORD, - PyTokenTypes.CLASS_KEYWORD, - PyTokenTypes.CONTINUE_KEYWORD, - PyTokenTypes.DEF_KEYWORD, - PyTokenTypes.DEL_KEYWORD, - PyTokenTypes.ELIF_KEYWORD, - PyTokenTypes.ELSE_KEYWORD, - PyTokenTypes.EXCEPT_KEYWORD, - PyTokenTypes.EXEC_KEYWORD, - PyTokenTypes.FINALLY_KEYWORD, - PyTokenTypes.FOR_KEYWORD, - PyTokenTypes.FROM_KEYWORD, - PyTokenTypes.GLOBAL_KEYWORD, - PyTokenTypes.IF_KEYWORD, - PyTokenTypes.IMPORT_KEYWORD, - PyTokenTypes.IN_KEYWORD, - PyTokenTypes.IS_KEYWORD, - PyTokenTypes.LAMBDA_KEYWORD, - PyTokenTypes.NOT_KEYWORD, - PyTokenTypes.OR_KEYWORD, - PyTokenTypes.PASS_KEYWORD, - PyTokenTypes.PRINT_KEYWORD, - PyTokenTypes.RAISE_KEYWORD, - PyTokenTypes.RETURN_KEYWORD, - PyTokenTypes.TRY_KEYWORD, - PyTokenTypes.WITH_KEYWORD, - PyTokenTypes.WHILE_KEYWORD, - PyTokenTypes.YIELD_KEYWORD, - PyTokenTypes.NONE_KEYWORD, - PyTokenTypes.TRUE_KEYWORD, - PyTokenTypes.FALSE_KEYWORD, - PyTokenTypes.NONLOCAL_KEYWORD, - PyTokenTypes.DEBUG_KEYWORD, - PyTokenTypes.ASYNC_KEYWORD, - PyTokenTypes.AWAIT_KEYWORD)); - - public static boolean isKeyword(PyElementType elementType) { - return KEYWORDS_ELEMENT_TYPES.contains(elementType); - } -} diff --git a/python-frontend/src/main/java/org/sonar/python/frontend/PythonParser.java b/python-frontend/src/main/java/org/sonar/python/frontend/PythonParser.java deleted file mode 100644 index e21974ea54..0000000000 --- a/python-frontend/src/main/java/org/sonar/python/frontend/PythonParser.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python.frontend; - -import com.intellij.core.CoreASTFactory; -import com.intellij.core.CoreFileTypeRegistry; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageASTFactory; -import com.intellij.lang.LanguageParserDefinitions; -import com.intellij.lang.MetaLanguage; -import com.intellij.lang.PsiBuilderFactory; -import com.intellij.lang.impl.PsiBuilderFactoryImpl; -import com.intellij.mock.MockApplication; -import com.intellij.mock.MockFileDocumentManagerImpl; -import com.intellij.mock.MockProject; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.impl.DocumentImpl; -import com.intellij.openapi.extensions.ExtensionPoint; -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.extensions.ExtensionsArea; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileTypes.FileTypeRegistry; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.impl.ProgressManagerImpl; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.StaticGetter; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiErrorElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiFileFactory; -import com.intellij.psi.PsiManager; -import com.intellij.psi.SyntaxTraverser; -import com.intellij.psi.impl.PsiFileFactoryImpl; -import com.intellij.psi.impl.PsiManagerImpl; -import com.intellij.util.containers.JBIterable; -import com.jetbrains.python.PythonDialectsTokenSetContributor; -import com.jetbrains.python.PythonFileType; -import com.jetbrains.python.PythonLanguage; -import com.jetbrains.python.PythonParserDefinition; -import com.jetbrains.python.PythonTokenSetContributor; -import com.jetbrains.python.psi.LanguageLevel; -import com.jetbrains.python.psi.PyFile; -import com.sonar.sslr.api.RecognitionException; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Modifier; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import org.jetbrains.annotations.NotNull; - -public class PythonParser { - - private static final Disposable TEST_DISPOSABLE = new TestDisposable(); - - private static final PsiFileFactory psiFileFactory = psiFileFactory(); - - public PyFile parse(String content) { - PyFile file = parseAs(content, LanguageLevel.PYTHON38); - if (errorElements(file).isEmpty()) { - return file; - } - file = parseAs(content, LanguageLevel.PYTHON27); - PsiErrorElement errorElement = errorElements(file).get(0); - if (errorElement == null) { - return file; - } - int lineNumber = new PythonTokenLocation(errorElement).startLine(); - throw new RecognitionException(lineNumber, errorElement.getErrorDescription()); - } - - private static JBIterable errorElements(PsiElement root) { - return SyntaxTraverser.psiTraverser(root).traverse().filter(PsiErrorElement.class); - } - - @NotNull - private static PyFile parseAs(String content, LanguageLevel languageLevel) { - PsiFile file = psiFileFactory.createFileFromText("test.py", PythonFileType.INSTANCE, normalizeEol(content), System.currentTimeMillis(), false, false); - file.getViewProvider().getVirtualFile().putUserData(LanguageLevel.KEY, languageLevel); - return (PyFile) file; - } - - @NotNull - public static String normalizeEol(String content) { - return content.replaceAll("\\r\\n?", "\n"); - } - - - public static PyFile parse(File file) { - String fileContent; - try { - fileContent = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new IllegalStateException(e); - } - return new PythonParser().parse(fileContent); - } - - private static PsiFileFactory psiFileFactory() { - CoreFileTypeRegistry fileTypeRegistry = new CoreFileTypeRegistry(); - fileTypeRegistry.registerFileType(PythonFileType.INSTANCE, "py"); - FileTypeRegistry.ourInstanceGetter = new StaticGetter<>(fileTypeRegistry); - - Disposable disposable = Disposer.newDisposable(); - - MockApplication application = new MockApplication(disposable); - FileDocumentManager fileDocMgr = new MockFileDocumentManagerImpl(DocumentImpl::new, null); - application.registerService(FileDocumentManager.class, fileDocMgr); - PsiBuilderFactoryImpl psiBuilderFactory = new PsiBuilderFactoryImpl(); - application.registerService(PsiBuilderFactory.class, psiBuilderFactory); - application.registerService(ProgressManager.class, ProgressManagerImpl.class); - ApplicationManager.setApplication(application, FileTypeRegistry.ourInstanceGetter, disposable); - - Extensions.getArea(null).registerExtensionPoint(MetaLanguage.EP_NAME.getName(), MetaLanguage.class.getName(), ExtensionPoint.Kind.INTERFACE); - Extensions.registerAreaClass("IDEA_PROJECT", null); - registerExtensionPoint(PythonDialectsTokenSetContributor.EP_NAME, PythonDialectsTokenSetContributor.class); - registerExtension(PythonDialectsTokenSetContributor.EP_NAME, new PythonTokenSetContributor()); - - MockProject project = new MockProject(null, disposable); - - LanguageParserDefinitions.INSTANCE.addExplicitExtension(PythonLanguage.getInstance(), new PythonParserDefinition()); - CoreASTFactory astFactory = new CoreASTFactory(); - LanguageASTFactory.INSTANCE.addExplicitExtension(PythonLanguage.getInstance(), astFactory); - LanguageASTFactory.INSTANCE.addExplicitExtension(Language.ANY, astFactory); - - // https://github.com/JetBrains/intellij-community/blob/93b632941e406178dd5c78fe4d8fdf7d8c357355/platform/testFramework/src/com/intellij/testFramework/ParsingTestCase.java - //new MockPsiManager() - PsiManager psiManager = new PsiManagerImpl(project, fileDocMgr, psiBuilderFactory, null, null, null); - return new PsiFileFactoryImpl(psiManager); - } - - protected static void registerExtension(@NotNull ExtensionPointName extensionPointName, @NotNull T t) { - registerExtension(Extensions.getRootArea(), extensionPointName, t); - } - - public static void registerExtension(@NotNull ExtensionsArea area, @NotNull ExtensionPointName name, @NotNull T t) { - registerExtensionPoint(area, name, (Class)t.getClass()); - area.getExtensionPoint(name.getName()).registerExtension(t, TEST_DISPOSABLE); - } - - private static void registerExtensionPoint(@NotNull ExtensionPointName extensionPointName, @NotNull Class aClass) { - registerExtensionPoint(Extensions.getRootArea(), extensionPointName, aClass); - } - - private static void registerExtensionPoint( - @NotNull ExtensionsArea area, - @NotNull ExtensionPointName extensionPointName, - @NotNull Class aClass - ) { - if (!area.hasExtensionPoint(extensionPointName)) { - ExtensionPoint.Kind kind = aClass.isInterface() || (aClass.getModifiers() & Modifier.ABSTRACT) != 0 ? ExtensionPoint.Kind.INTERFACE : ExtensionPoint.Kind.BEAN_CLASS; - area.registerExtensionPoint(extensionPointName, aClass.getName(), kind, TEST_DISPOSABLE); - } - } - - protected static class TestDisposable implements Disposable { - @Override - public void dispose() { - // Nothing to do - } - } - -} diff --git a/python-frontend/src/main/java/org/sonar/python/frontend/PythonTokenLocation.java b/python-frontend/src/main/java/org/sonar/python/frontend/PythonTokenLocation.java deleted file mode 100644 index 0633d02dd1..0000000000 --- a/python-frontend/src/main/java/org/sonar/python/frontend/PythonTokenLocation.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python.frontend; - -import com.intellij.openapi.editor.Document; -import com.intellij.psi.PsiElement; -import org.jetbrains.annotations.NotNull; - -public class PythonTokenLocation { - private final int startLine; - private final int startLineOffset; - private final int endLine; - private final int endLineOffset; - - public PythonTokenLocation(@NotNull PsiElement element) { - this(element.getTextRange().getStartOffset(), element.getTextRange().getEndOffset(), element.getContainingFile().getViewProvider().getDocument()); - } - - public PythonTokenLocation(int startOffset, int endOffset, Document psiDocument) { - startLine = psiDocument.getLineNumber(startOffset); - int startLineNumberOffset = psiDocument.getLineStartOffset(startLine); - startLineOffset = startOffset - startLineNumberOffset; - endLine = psiDocument.getLineNumber(endOffset); - int endLineNumberOffset = psiDocument.getLineStartOffset(endLine); - endLineOffset = endOffset - endLineNumberOffset; - } - - public int startLine() { - return startLine + 1; - } - - public int startLineOffset() { - return startLineOffset; - } - - public int endLine() { - return endLine + 1; - } - - public int endLineOffset() { - return endLineOffset; - } -} diff --git a/python-frontend/src/test/java/org/sonar/python/frontend/PythonKeywordTest.java b/python-frontend/src/test/java/org/sonar/python/frontend/PythonKeywordTest.java deleted file mode 100644 index 8021f7daea..0000000000 --- a/python-frontend/src/test/java/org/sonar/python/frontend/PythonKeywordTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python.frontend; - -import com.jetbrains.python.PyTokenTypes; -import org.junit.Test; - -import static org.junit.Assert.*; -import static org.assertj.core.api.Assertions.assertThat; - -public class PythonKeywordTest { - - @Test - public void isKeyword() { - assertThat(PythonKeyword.isKeyword(PyTokenTypes.DOCSTRING)).isFalse(); - assertThat(PythonKeyword.isKeyword(PyTokenTypes.IF_KEYWORD)).isTrue(); - } -} diff --git a/python-frontend/src/test/java/org/sonar/python/frontend/PythonParserTest.java b/python-frontend/src/test/java/org/sonar/python/frontend/PythonParserTest.java deleted file mode 100644 index b5fbeff163..0000000000 --- a/python-frontend/src/test/java/org/sonar/python/frontend/PythonParserTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python.frontend; - -import com.intellij.openapi.editor.Document; -import com.intellij.psi.PsiElement; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.psi.PyCallExpression; -import com.jetbrains.python.psi.PyExpressionStatement; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFormattedStringElement; -import com.jetbrains.python.psi.PyPrintStatement; -import com.jetbrains.python.psi.PyRecursiveElementVisitor; -import com.jetbrains.python.psi.PyStatement; -import com.sonar.sslr.api.RecognitionException; -import java.util.ArrayList; -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PythonParserTest { - - private final PythonParser parser = new PythonParser(); - - @Test - public void parsing_error() { - try { - parser.parse("x = 1\nx =\nx = 2"); - Assertions.fail("should have raised an exception"); - } catch (RecognitionException e) { - assertThat(e.getLine()).isEqualTo(2); - assertThat(e.getMessage()).isEqualTo("Expression expected"); - } - } - - @Test - public void print_statement() { - PyFile pyFile = parser.parse("print 42"); - PyStatement statement = pyFile.getStatements().get(0); - assertThat(statement).isInstanceOf(PyPrintStatement.class); - } - - @Test - public void print_with_parentheses() { - PyFile pyFile = parser.parse("print(42)"); - PyStatement statement = pyFile.getStatements().get(0); - assertThat(statement).isInstanceOf(PyExpressionStatement.class); - assertThat(statement.getFirstChild()).isInstanceOf(PyCallExpression.class); - } - - @Test - public void call_expressions() { - PyFile pyFile = parser.parse("s = 'abc'\nfoo(s, 42)\nbar(43)"); - List callExpressions = new ArrayList<>(); - pyFile.accept(new PyRecursiveElementVisitor() { - @Override - public void visitPyCallExpression(PyCallExpression node) { - callExpressions.add(node + "::" + node.getArguments(null)); - super.visitPyCallExpression(node); - } - }); - assertThat(callExpressions).containsExactly( - "PyCallExpression: foo::[PyReferenceExpression: s, PyNumericLiteralExpression]", - "PyCallExpression: bar::[PyNumericLiteralExpression]"); - } - - @Test - public void python3_f_string() { - PyFile pyFile = parser.parse("f\"Hello {name}!\""); - PyFormattedStringElement stringElement = PsiTreeUtil.getParentOfType(pyFile.findElementAt(0), PyFormattedStringElement.class); - assertThat(stringElement.getDecodedFragments().stream().map(pair -> pair.second)).containsExactly("Hello ", "{name}", "!"); - } - - @Test - public void line_separator() { - PyFile pyFile = parser.parse("print(42)\r\nprint(43)"); - PsiElement secondPrint = pyFile.getLastChild(); - Document document = secondPrint.getContainingFile().getViewProvider().getDocument(); - assertThat(document.getLineNumber(secondPrint.getTextRange().getStartOffset())).isEqualTo(1); - - pyFile = parser.parse("print(42)\rprint(43)"); - secondPrint = pyFile.getLastChild(); - document = secondPrint.getContainingFile().getViewProvider().getDocument(); - assertThat(document.getLineNumber(secondPrint.getTextRange().getStartOffset())).isEqualTo(1); - - pyFile = parser.parse("print(42)\nprint(43)"); - secondPrint = pyFile.getLastChild(); - document = secondPrint.getContainingFile().getViewProvider().getDocument(); - assertThat(document.getLineNumber(secondPrint.getTextRange().getStartOffset())).isEqualTo(1); - } -} diff --git a/python-frontend/src/test/java/org/sonar/python/frontend/PythonTokenLocationTest.java b/python-frontend/src/test/java/org/sonar/python/frontend/PythonTokenLocationTest.java deleted file mode 100644 index 82547376d7..0000000000 --- a/python-frontend/src/test/java/org/sonar/python/frontend/PythonTokenLocationTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python.frontend; - -import com.intellij.psi.PsiElement; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PythonTokenLocationTest { - private PythonParser parser = new PythonParser(); - - @Test - public void test_multiline() { - PythonTokenLocation tokenLocation = new PythonTokenLocation(getTokens("'''first line\nsecond'''")[0]); - assertOffsets(tokenLocation, 1, 0, 2, 9); - } - - @Test - public void test_newline_token() { - PythonTokenLocation tokenLocation = new PythonTokenLocation(getTokens("foo\n")[1]); - assertOffsets(tokenLocation, 1, 3, 2, 0); - } - - @Test - public void test_one_line() { - PsiElement[] tokens = getTokens("'''first line'''"); - PythonTokenLocation tokenLocation = new PythonTokenLocation(tokens[0]); - assertOffsets(tokenLocation, 1, 0, 1, 16); - - tokenLocation = new PythonTokenLocation(getTokens("foo")[0]); - assertOffsets(tokenLocation, 1, 0, 1, 3); - } - - @Test - public void test_comment() { - PythonTokenLocation commentLocation = new PythonTokenLocation(getTokens("#comment\n")[0]); - assertOffsets(commentLocation, 1, 0, 1, 8); - } - - private static void assertOffsets(PythonTokenLocation tokenLocation, int startLine, int startLineOffset, int endLine, int endLineOffset) { - assertThat(tokenLocation.startLine()).as("start line").isEqualTo(startLine); - assertThat(tokenLocation.startLineOffset()).as("start line offset").isEqualTo(startLineOffset); - assertThat(tokenLocation.endLine()).as("end line").isEqualTo(endLine); - assertThat(tokenLocation.endLineOffset()).as("end line offset").isEqualTo(endLineOffset); - } - - private PsiElement[] getTokens(String toLex) { - return parser.parse(toLex).getChildren(); - } -} diff --git a/python-squid/pom.xml b/python-squid/pom.xml index cd45d9f079..033615532d 100644 --- a/python-squid/pom.xml +++ b/python-squid/pom.xml @@ -25,13 +25,6 @@ org.sonarsource.sonarqube sonar-plugin-api - - - ${project.groupId} - python-frontend - ${project.version} - - org.sonarsource.sslr sslr-testing-harness diff --git a/python-squid/src/main/java/org/sonar/python/IssueLocation.java b/python-squid/src/main/java/org/sonar/python/IssueLocation.java index 4e755e2ba8..3a00f0eba1 100644 --- a/python-squid/src/main/java/org/sonar/python/IssueLocation.java +++ b/python-squid/src/main/java/org/sonar/python/IssueLocation.java @@ -19,12 +19,10 @@ */ package org.sonar.python; -import com.intellij.psi.PsiElement; import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.sonar.python.frontend.PythonTokenLocation; public abstract class IssueLocation { @@ -54,14 +52,6 @@ public static IssueLocation preciseLocation(AstNode startNode, @Nullable String return new PreciseIssueLocation(startNode, message); } - public static IssueLocation preciseLocation(PsiElement element, @Nullable String message) { - return new PsiIssueLocation(element, message); - } - - public static IssueLocation preciseLocation(PsiElement startElement, PsiElement endElement, @Nullable String message) { - return new PsiIssueLocation(startElement, endElement, message); - } - @CheckForNull public String message() { return message; @@ -75,42 +65,6 @@ public String message() { public abstract int endLineOffset(); - private static class PsiIssueLocation extends IssueLocation { - private final PythonTokenLocation startLocation; - private final PythonTokenLocation endLocation; - - PsiIssueLocation(PsiElement element, @Nullable String message) { - this(element, element, message); - } - - PsiIssueLocation(PsiElement startElement, PsiElement endElement, @Nullable String message) { - super(message); - this.startLocation = new PythonTokenLocation(startElement); - this.endLocation = new PythonTokenLocation(endElement); - } - - @Override - public int startLine() { - return startLocation.startLine(); - } - - @Override - public int startLineOffset() { - return startLocation.startLineOffset(); - } - - @Override - public int endLine() { - return endLocation.endLine(); - } - - @Override - public int endLineOffset() { - return endLocation.endLineOffset(); - } - - } - private static class PreciseIssueLocation extends IssueLocation { private final Token firstToken; 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 98637a9307..4423ed33da 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonCheck.java +++ b/python-squid/src/main/java/org/sonar/python/PythonCheck.java @@ -19,7 +19,6 @@ */ 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; @@ -30,7 +29,7 @@ import java.util.Set; import javax.annotation.Nullable; -public abstract class PythonCheck extends PythonVisitor implements SubscriptionCheck { +public abstract class PythonCheck extends PythonVisitor { protected final PreciseIssue addIssue(AstNode node, @Nullable String message) { PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.preciseLocation(node, message)); @@ -67,7 +66,7 @@ public static class PreciseIssue { private Integer cost; private final List secondaryLocations; - PreciseIssue(PythonCheck check, IssueLocation primaryLocation) { + private PreciseIssue(PythonCheck check, IssueLocation primaryLocation) { this.check = check; this.primaryLocation = primaryLocation; this.secondaryLocations = new ArrayList<>(); @@ -97,11 +96,6 @@ 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; } @@ -114,8 +108,4 @@ public PythonCheck check() { public static Set immutableSet(T... el) { return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(el))); } - - @Override - public void initialize(Context context) { - } } diff --git a/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java b/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java index 08ecfa192a..2d49a3b37e 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java +++ b/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java @@ -19,12 +19,10 @@ */ package org.sonar.python; -import com.intellij.psi.PsiElement; import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.RecognitionException; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; import org.sonar.python.PythonCheck.PreciseIssue; import org.sonar.python.semantic.SymbolTable; import org.sonar.python.semantic.SymbolTableBuilderVisitor; @@ -74,16 +72,6 @@ public void addIssue(PreciseIssue issue) { issues.add(issue); } - public PreciseIssue addIssue(PythonCheck check, IssueLocation issueLocation) { - PreciseIssue issue = new PreciseIssue(check, issueLocation); - issues.add(issue); - return issue; - } - - public PreciseIssue addIssue(PythonCheck check, PsiElement element, @Nullable String message) { - return addIssue(check, IssueLocation.preciseLocation(element, message)); - } - public List getIssues() { return issues; } diff --git a/python-squid/src/main/java/org/sonar/python/SubscriptionCheck.java b/python-squid/src/main/java/org/sonar/python/SubscriptionCheck.java deleted file mode 100644 index 1e024f5d3f..0000000000 --- a/python-squid/src/main/java/org/sonar/python/SubscriptionCheck.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python; - -import com.intellij.psi.tree.IElementType; -import java.util.function.Consumer; - -public interface SubscriptionCheck { - - void initialize(Context context); - - interface Context { - - void registerSyntaxNodeConsumer(IElementType elementType, Consumer consumer); - - } - -} diff --git a/python-squid/src/main/java/org/sonar/python/SubscriptionContext.java b/python-squid/src/main/java/org/sonar/python/SubscriptionContext.java deleted file mode 100644 index aab901eaed..0000000000 --- a/python-squid/src/main/java/org/sonar/python/SubscriptionContext.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python; - -import com.intellij.psi.PsiElement; -import javax.annotation.Nullable; -import org.sonar.python.PythonCheck.PreciseIssue; - -public interface SubscriptionContext { - - PsiElement syntaxNode(); - - PreciseIssue addIssue(PsiElement element, @Nullable String message); - - PreciseIssue addIssue(IssueLocation issueLocation); - - PreciseIssue addFileIssue(@Nullable String message); - -} diff --git a/python-squid/src/main/java/org/sonar/python/SubscriptionVisitor.java b/python-squid/src/main/java/org/sonar/python/SubscriptionVisitor.java deleted file mode 100644 index 8f3b2b7545..0000000000 --- a/python-squid/src/main/java/org/sonar/python/SubscriptionVisitor.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python; - -import com.intellij.psi.PsiElement; -import com.intellij.psi.tree.IElementType; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyRecursiveElementVisitor; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import javax.annotation.Nullable; -import org.sonar.python.PythonCheck.PreciseIssue; - -public class SubscriptionVisitor extends PyRecursiveElementVisitor { - - private final Map> consumers = new HashMap<>(); - private final PythonVisitorContext pythonVisitorContext; - private PsiElement currentElement; - - public static void analyze(Collection checks, PythonVisitorContext pythonVisitorContext, PyFile pyFile) { - SubscriptionVisitor subscriptionVisitor = new SubscriptionVisitor(checks, pythonVisitorContext); - pyFile.accept(subscriptionVisitor); - } - - private SubscriptionVisitor(Collection checks, PythonVisitorContext pythonVisitorContext) { - this.pythonVisitorContext = pythonVisitorContext; - for (PythonCheck check : checks) { - check.initialize((elementType, consumer) -> { - List elementConsumers = consumers.computeIfAbsent(elementType, c -> new ArrayList<>()); - elementConsumers.add(new ConsumerWrapper(check, consumer)); - }); - } - } - - @Override - public void visitElement(PsiElement element) { - currentElement = element; - List elementConsumers = consumers.get(element.getNode().getElementType()); - if (elementConsumers != null) { - for (ConsumerWrapper consumer : elementConsumers) { - consumer.execute(); - } - } - super.visitElement(element); - } - - private class ConsumerWrapper implements SubscriptionContext { - private final PythonCheck check; - private final Consumer consumer; - - ConsumerWrapper(PythonCheck check, Consumer consumer) { - this.check = check; - this.consumer = consumer; - } - - public void execute() { - consumer.accept(this); - } - - @Override - public PsiElement syntaxNode() { - return SubscriptionVisitor.this.currentElement; - } - - @Override - public PreciseIssue addIssue(PsiElement element, @Nullable String message) { - return pythonVisitorContext.addIssue(check, element, message); - } - - @Override - public PreciseIssue addIssue(IssueLocation issueLocation) { - return pythonVisitorContext.addIssue(check, issueLocation); - } - - @Override - public PreciseIssue addFileIssue(@Nullable String message) { - return pythonVisitorContext.addIssue(check, IssueLocation.atFileLevel(message)); - } - } -} diff --git a/python-squid/src/main/java/org/sonar/python/metrics/CognitiveComplexityVisitor.java b/python-squid/src/main/java/org/sonar/python/metrics/CognitiveComplexityVisitor.java index d440169d70..78fe2f0341 100644 --- a/python-squid/src/main/java/org/sonar/python/metrics/CognitiveComplexityVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/metrics/CognitiveComplexityVisitor.java @@ -19,37 +19,31 @@ */ package org.sonar.python.metrics; -import com.intellij.psi.PsiElement; -import com.intellij.psi.tree.IElementType; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.PyBinaryExpression; -import com.jetbrains.python.psi.PyElementType; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.PyRecursiveElementVisitor; -import com.jetbrains.python.psi.PyReturnStatement; -import com.jetbrains.python.psi.PyStatementListContainer; -import java.util.ArrayList; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; import java.util.Arrays; +import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.annotation.Nullable; +import org.sonar.python.PythonVisitor; +import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.PythonKeyword; +import org.sonar.python.api.PythonPunctuator; -public class CognitiveComplexityVisitor extends PyRecursiveElementVisitor { +public class CognitiveComplexityVisitor extends PythonVisitor { private int complexity = 0; private Deque nestingLevelStack = new LinkedList<>(); - private Set alreadyConsideredOperators = new HashSet<>(); @Nullable private final SecondaryLocationConsumer secondaryLocationConsumer; public interface SecondaryLocationConsumer { - void consume(PsiElement element, String message); + void consume(AstNode node, String message); } CognitiveComplexityVisitor(@Nullable SecondaryLocationConsumer secondaryLocationConsumer) { @@ -57,9 +51,9 @@ public interface SecondaryLocationConsumer { nestingLevelStack.push(new NestingLevel()); } - public static int complexity(PsiElement element, @Nullable SecondaryLocationConsumer secondaryLocationConsumer) { + public static int complexity(AstNode node, @Nullable SecondaryLocationConsumer secondaryLocationConsumer) { CognitiveComplexityVisitor visitor = new CognitiveComplexityVisitor(secondaryLocationConsumer); - element.accept(visitor); + visitor.scanNode(node); return visitor.complexity; } @@ -67,116 +61,79 @@ public int getComplexity() { return complexity; } - private static final Set TYPES_INCREMENTING_WITH_NESTING = new HashSet<>(Arrays.asList( - PyElementTypes.IF_STATEMENT, - PyElementTypes.WHILE_STATEMENT, - PyElementTypes.FOR_STATEMENT, - PyElementTypes.EXCEPT_PART - )); - - private static final Set TYPES_INCREMENTING_WITHOUT_NESTING = new HashSet<>(Arrays.asList( - PyElementTypes.IF_PART_ELIF, - PyElementTypes.ELSE_PART - )); - - private static final Set NON_NESTING_STATEMENT_LISTS = new HashSet<>(Arrays.asList( - PyElementTypes.TRY_PART, - PyElementTypes.FINALLY_PART, - PyElementTypes.CLASS_DECLARATION, - PyElementTypes.FUNCTION_DECLARATION, - PyElementTypes.WITH_STATEMENT - )); - @Override - public void visitElement(PsiElement element) { - IElementType elementType = element.getNode().getElementType(); - - if (TYPES_INCREMENTING_WITH_NESTING.contains(elementType)) { - incrementWithNesting(element.getNode().findLeafElementAt(0).getPsi()); - } else if (TYPES_INCREMENTING_WITHOUT_NESTING.contains(elementType)) { - incrementWithoutNesting(element.getNode().findLeafElementAt(0).getPsi()); - } else if (isLogicalBinaryExpression(element)) { - visitLogicalBinaryExpression((PyBinaryExpression) element); - } + public Set subscribedKinds() { + return Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + PythonGrammar.IF_STMT, + PythonKeyword.ELIF, + PythonKeyword.ELSE, - if (elementType == PyElementTypes.FUNCTION_DECLARATION || elementType == PyElementTypes.CLASS_DECLARATION) { - nestingLevelStack.push(new NestingLevel(nestingLevelStack.peek(), element)); - } else if (isStatementListIncrementingNestingLevel(element)) { - nestingLevelStack.peek().increment(); - } else if (elementType == PyElementTypes.CONDITIONAL_EXPRESSION) { - incrementWithNesting(element.getNode().findChildByType(PyTokenTypes.IF_KEYWORD).getPsi()); - nestingLevelStack.peek().increment(); - } + PythonGrammar.WHILE_STMT, + PythonGrammar.FOR_STMT, + PythonGrammar.EXCEPT_CLAUSE, - super.visitElement(element); + PythonGrammar.AND_TEST, + PythonGrammar.OR_TEST, - if (elementType == PyElementTypes.FUNCTION_DECLARATION || elementType == PyElementTypes.CLASS_DECLARATION) { - nestingLevelStack.pop(); - } else if (isStatementListIncrementingNestingLevel(element)) { - nestingLevelStack.peek().decrement(); - } else if (elementType == PyElementTypes.CONDITIONAL_EXPRESSION) { - nestingLevelStack.peek().decrement(); - } - } + PythonGrammar.TEST, - private static boolean isLogicalBinaryExpression(PsiElement element) { - if (element instanceof PyBinaryExpression) { - PyBinaryExpression binaryExpression = (PyBinaryExpression) element; - PyElementType operator = binaryExpression.getOperator(); - return operator == PyTokenTypes.AND_KEYWORD || operator == PyTokenTypes.OR_KEYWORD; - } - return false; + PythonGrammar.FUNCDEF, + PythonGrammar.CLASSDEF, + PythonGrammar.SUITE))); } - private void visitLogicalBinaryExpression(PyBinaryExpression tree) { - if (alreadyConsideredOperators.contains(tree.getPsiOperator())) { - return; - } - - List operators = new ArrayList<>(); - flattenOperators(tree, operators); - - PsiElement previous = null; - for (PsiElement operator : operators) { - if (previous == null || !previous.getNode().getElementType().equals(operator.getNode().getElementType())) { - incrementWithoutNesting(operator); + @Override + public void visitNode(AstNode astNode) { + if (astNode.is(PythonGrammar.FUNCDEF, PythonGrammar.CLASSDEF)) { + nestingLevelStack.push(new NestingLevel(nestingLevelStack.peek(), astNode)); + } else if (astNode.is(PythonGrammar.SUITE)) { + if (isSuiteIncrementsNestingLevel(astNode)) { + nestingLevelStack.peek().increment(); } - previous = operator; - alreadyConsideredOperators.add(operator); + } else if (astNode.is(PythonGrammar.IF_STMT, PythonGrammar.WHILE_STMT, PythonGrammar.FOR_STMT, PythonGrammar.EXCEPT_CLAUSE)) { + incrementWithNesting(astNode.getFirstChild()); + } else if (astNode.is(PythonKeyword.ELIF) || (astNode.is(PythonKeyword.ELSE) && astNode.getNextSibling().is(PythonPunctuator.COLON))) { + incrementWithoutNesting(astNode); + } else if (astNode.is(PythonGrammar.AND_TEST, PythonGrammar.OR_TEST)) { + incrementWithoutNesting(astNode.getFirstChild(PythonKeyword.AND, PythonKeyword.OR)); + } else if (astNode.is(PythonGrammar.TEST) && astNode.hasDirectChildren(PythonKeyword.IF)) { + // conditional expression + incrementWithNesting(astNode.getFirstChild(PythonKeyword.IF)); + nestingLevelStack.peek().increment(); } } - private static void flattenOperators(PyBinaryExpression binaryExpression, List operators) { - PyExpression left = binaryExpression.getLeftExpression(); - if (isLogicalBinaryExpression(left)) { - flattenOperators((PyBinaryExpression) left, operators); - } - - operators.add(binaryExpression.getPsiOperator()); - - PyExpression right = binaryExpression.getRightExpression(); - if (isLogicalBinaryExpression(right)) { - flattenOperators((PyBinaryExpression) right, operators); + @Override + public void leaveNode(AstNode astNode) { + if (astNode.is(PythonGrammar.FUNCDEF, PythonGrammar.CLASSDEF)) { + nestingLevelStack.pop(); + } else if (astNode.is(PythonGrammar.SUITE)) { + if (isSuiteIncrementsNestingLevel(astNode)) { + nestingLevelStack.peek().decrement(); + } + } else if (astNode.is(PythonGrammar.TEST) && astNode.hasDirectChildren(PythonKeyword.IF)) { + // conditional expression + nestingLevelStack.peek().decrement(); } } - private static boolean isStatementListIncrementingNestingLevel(PsiElement element) { - if (element instanceof PyStatementListContainer) { - IElementType elementType = element.getNode().getElementType(); - return !NON_NESTING_STATEMENT_LISTS.contains(elementType); + private static boolean isSuiteIncrementsNestingLevel(AstNode astNode) { + AstNode previousSibling = astNode.getPreviousSibling().getPreviousSibling(); + if (previousSibling.is(PythonKeyword.TRY, PythonKeyword.FINALLY)) { + return false; } - return false; + return !astNode.getParent().is(PythonGrammar.CLASSDEF, PythonGrammar.FUNCDEF, PythonGrammar.WITH_STMT); } - private void incrementWithNesting(PsiElement secondaryLocationNode) { + private void incrementWithNesting(AstNode secondaryLocationNode) { incrementComplexity(secondaryLocationNode, 1 + nestingLevelStack.peek().level()); } - private void incrementWithoutNesting(PsiElement secondaryLocationNode) { + private void incrementWithoutNesting(AstNode secondaryLocationNode) { incrementComplexity(secondaryLocationNode, 1); } - private void incrementComplexity(PsiElement secondaryLocationNode, int currentNodeComplexity) { + private void incrementComplexity(AstNode secondaryLocationNode, int currentNodeComplexity) { if (secondaryLocationConsumer != null) { secondaryLocationConsumer.consume(secondaryLocationNode, secondaryMessage(currentNodeComplexity)); } @@ -194,18 +151,18 @@ private static String secondaryMessage(int complexity) { private static class NestingLevel { @Nullable - private PsiElement element; + private AstNode astNode; private int level; private NestingLevel() { - element = null; + astNode = null; level = 0; } - private NestingLevel(NestingLevel parent, PsiElement element) { - this.element = element; - if (isFunction()) { - if (parent.isWrapperFunction(element)) { + private NestingLevel(NestingLevel parent, AstNode astNode) { + this.astNode = astNode; + if (astNode.is(PythonGrammar.FUNCDEF)) { + if (parent.isWrapperFunction(astNode)) { level = parent.level; } else if (parent.isFunction()) { level = parent.level + 1; @@ -219,24 +176,43 @@ private NestingLevel(NestingLevel parent, PsiElement element) { } private boolean isFunction() { - return element != null && element.getNode().getElementType() == PyElementTypes.FUNCTION_DECLARATION; + return astNode != null && astNode.is(PythonGrammar.FUNCDEF); } - private boolean isWrapperFunction(PsiElement childFunction) { - if (isFunction()) { - return Arrays.stream(((PyFunction) element).getStatementList().getStatements()) - .filter(statement -> statement != childFunction) + private boolean isWrapperFunction(AstNode childFunction) { + if(astNode != null && astNode.is(PythonGrammar.FUNCDEF)) { + AstNode childStatement = childFunction.getParent().getParent(); + return astNode.getFirstChild(PythonGrammar.SUITE) + .getChildren(PythonGrammar.STATEMENT) + .stream() + .filter(statement -> statement != childStatement) .allMatch(NestingLevel::isSimpleReturn); } return false; } - private static boolean isSimpleReturn(PsiElement statement) { - if (statement instanceof PyReturnStatement) { - PyReturnStatement returnStatement = (PyReturnStatement) statement; - return returnStatement.getExpression().getNode().getElementType() == PyElementTypes.REFERENCE_EXPRESSION; + private static boolean isSimpleReturn(AstNode statement) { + AstNode returnStatement = lookupOnlyChild(statement.getFirstChild(PythonGrammar.STMT_LIST), + PythonGrammar.SIMPLE_STMT, PythonGrammar.RETURN_STMT); + return returnStatement != null && + lookupOnlyChild(returnStatement.getFirstChild(PythonGrammar.TESTLIST), + PythonGrammar.TEST, PythonGrammar.ATOM, PythonGrammar.NAME) != null; + } + + @Nullable + private static AstNode lookupOnlyChild(@Nullable AstNode parent, AstNodeType... types) { + if (parent == null) { + return null; } - return false; + AstNode result = parent; + for (AstNodeType type : types) { + List children = result.getChildren(); + if (children.size() != 1 || !children.get(0).is(type)) { + return null; + } + result = children.get(0); + } + return result; } private int level() { diff --git a/python-squid/src/main/java/org/sonar/python/metrics/ComplexityVisitor.java b/python-squid/src/main/java/org/sonar/python/metrics/ComplexityVisitor.java index 9b230ea7d3..1d33b58a4d 100644 --- a/python-squid/src/main/java/org/sonar/python/metrics/ComplexityVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/metrics/ComplexityVisitor.java @@ -19,44 +19,45 @@ */ package org.sonar.python.metrics; -import com.intellij.psi.PsiElement; -import com.intellij.psi.tree.IElementType; -import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.PyRecursiveElementVisitor; -import java.util.Arrays; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; import java.util.HashSet; import java.util.Set; +import org.sonar.python.PythonVisitor; +import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.PythonKeyword; -public class ComplexityVisitor extends PyRecursiveElementVisitor { +public class ComplexityVisitor extends PythonVisitor { private int complexity; - public static int complexity(PsiElement element) { - ComplexityVisitor visitor = isFunctionDeclaration(element) ? new FunctionComplexityVisitor() : new ComplexityVisitor(); - element.accept(visitor); + public static int complexity(AstNode node) { + ComplexityVisitor visitor = node.is(PythonGrammar.FUNCDEF) ? new FunctionComplexityVisitor() : new ComplexityVisitor(); + visitor.scanNode(node); return visitor.complexity; } - private static boolean isFunctionDeclaration(PsiElement element) { - return PyElementTypes.FUNCTION_DECLARATION.equals(element.getNode().getElementType()); + @Override + public Set subscribedKinds() { + Set set = new HashSet<>(); + set.add(PythonGrammar.FUNCDEF); + set.add(PythonGrammar.FOR_STMT); + set.add(PythonGrammar.WHILE_STMT); + set.add(PythonKeyword.IF); + set.add(PythonKeyword.AND); + set.add(PythonKeyword.OR); + return Collections.unmodifiableSet(set); } - private static final Set COMPLEXITY_TYPES = new HashSet<>(Arrays.asList( - PyElementTypes.FUNCTION_DECLARATION, - PyElementTypes.FOR_STATEMENT, - PyElementTypes.WHILE_STATEMENT, - PyTokenTypes.IF_KEYWORD, - PyTokenTypes.AND_KEYWORD, - PyTokenTypes.OR_KEYWORD - )); + @Override + public void visitFile(AstNode node) { + complexity = 0; + } @Override - public void visitElement(PsiElement element) { - if (COMPLEXITY_TYPES.contains(element.getNode().getElementType())) { - complexity++; - } - super.visitElement(element); + public void visitNode(AstNode node) { + complexity++; } public int getComplexity() { @@ -68,14 +69,18 @@ private static class FunctionComplexityVisitor extends ComplexityVisitor { private int functionNestingLevel = 0; @Override - public void visitElement(PsiElement element) { - if (isFunctionDeclaration(element)) { + public void visitNode(AstNode node) { + if (node.is(PythonGrammar.FUNCDEF)) { functionNestingLevel++; } if (functionNestingLevel == 1) { - super.visitElement(element); + super.visitNode(node); } - if (isFunctionDeclaration(element)) { + } + + @Override + public void leaveNode(AstNode node) { + if (node.is(PythonGrammar.FUNCDEF)) { functionNestingLevel--; } } diff --git a/python-squid/src/main/java/org/sonar/python/metrics/FileLinesVisitor.java b/python-squid/src/main/java/org/sonar/python/metrics/FileLinesVisitor.java new file mode 100644 index 0000000000..fc884183e0 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/metrics/FileLinesVisitor.java @@ -0,0 +1,190 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.python.metrics; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import com.sonar.sslr.api.GenericTokenType; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.python.DocstringExtractor; +import org.sonar.python.PythonVisitor; +import org.sonar.python.TokenLocation; +import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.PythonKeyword; +import org.sonar.python.api.PythonTokenType; + +/** + * Visitor that computes {@link CoreMetrics#NCLOC_DATA_KEY} and {@link CoreMetrics#COMMENT_LINES} metrics used by the DevCockpit. + */ +public class FileLinesVisitor extends PythonVisitor { + + private static final PythonCommentAnalyser COMMENT_ANALYSER = new PythonCommentAnalyser(); + private static final Set EXECUTABLE_LINE_KINDS = executableLineKinds(); + + private boolean seenFirstToken; + + private final boolean ignoreHeaderComments; + + private Set noSonar = new HashSet<>(); + private Set linesOfCode = new HashSet<>(); + private Set linesOfComments = new HashSet<>(); + private Set linesOfDocstring = new HashSet<>(); + private Set executableLines = new HashSet<>(); + + public FileLinesVisitor(boolean ignoreHeaderComments) { + this.ignoreHeaderComments = ignoreHeaderComments; + } + + private static Set executableLineKinds() { + Set kinds = new HashSet<>(); + kinds.add(PythonGrammar.STATEMENT); + kinds.add(PythonKeyword.ELIF); + kinds.add(PythonKeyword.EXCEPT); + return Collections.unmodifiableSet(kinds); + } + + @Override + public Set subscribedKinds() { + Set kinds = new HashSet<>(); + kinds.addAll(DocstringExtractor.DOCUMENTABLE_NODE_TYPES); + kinds.addAll(EXECUTABLE_LINE_KINDS); + return kinds; + } + + @Override + public void visitFile(AstNode astNode) { + noSonar.clear(); + linesOfCode.clear(); + linesOfComments.clear(); + linesOfDocstring.clear(); + executableLines.clear(); + seenFirstToken = false; + } + + @Override + public void visitNode(AstNode astNode) { + if (DocstringExtractor.DOCUMENTABLE_NODE_TYPES.contains(astNode.getType())) { + Token docstringToken = DocstringExtractor.extractDocstring(astNode); + if (docstringToken != null) { + TokenLocation location = new TokenLocation(docstringToken); + for (int line = location.startLine(); line <= location.endLine(); line++) { + linesOfDocstring.add(line); + } + } + } + if (EXECUTABLE_LINE_KINDS.contains(astNode.getType())) { + executableLines.add(astNode.getTokenLine()); + } + } + + /** + * Gets the lines of codes and lines of comments (with character #). + * Does not get the lines of docstrings. + */ + @Override + public void visitToken(Token token) { + if (token.getType().equals(GenericTokenType.EOF)) { + return; + } + + if (!token.getType().equals(PythonTokenType.DEDENT) && !token.getType().equals(PythonTokenType.INDENT) && !token.getType().equals(PythonTokenType.NEWLINE)) { + // Handle all the lines of the token + String[] tokenLines = token.getValue().split("\n", -1); + for (int line = token.getLine(); line < token.getLine() + tokenLines.length; line++) { + linesOfCode.add(line); + } + } + + if (ignoreHeaderComments && !seenFirstToken) { + seenFirstToken = true; + return; + } + + for (Trivia trivia : token.getTrivia()) { + if (trivia.isComment()) { + visitComment(trivia); + } + } + } + + public void visitComment(Trivia trivia) { + String[] commentLines = COMMENT_ANALYSER.getContents(trivia.getToken().getOriginalValue()) + .split("(\r)?\n|\r", -1); + int line = trivia.getToken().getLine(); + + for (String commentLine : commentLines) { + if (commentLine.contains("NOSONAR")) { + linesOfComments.remove(line); + noSonar.add(line); + } else if (!COMMENT_ANALYSER.isBlank(commentLine) && !noSonar.contains(line)) { + linesOfComments.add(line); + } + line++; + } + } + + @Override + public void leaveFile(AstNode astNode) { + // account for the docstring lines + for (Integer line : linesOfDocstring) { + executableLines.remove(line); + linesOfCode.remove(line); + linesOfComments.add(line); + } + } + + public Set getLinesWithNoSonar() { + return Collections.unmodifiableSet(new HashSet<>(noSonar)); + } + + public Set getLinesOfCode() { + return Collections.unmodifiableSet(new HashSet<>(linesOfCode)); + } + + public int getCommentLineCount() { + return linesOfComments.size(); + } + + public Set getExecutableLines() { + return Collections.unmodifiableSet(new HashSet<>(executableLines)); + } + + private static class PythonCommentAnalyser { + + public boolean isBlank(String line) { + for (int i = 0; i < line.length(); i++) { + if (Character.isLetterOrDigit(line.charAt(i))) { + return false; + } + } + return true; + } + + public String getContents(String comment) { + // Comment always starts with "#" + return comment.substring(comment.indexOf('#')); + } + } +} diff --git a/python-squid/src/main/java/org/sonar/python/metrics/FileMetrics.java b/python-squid/src/main/java/org/sonar/python/metrics/FileMetrics.java index af66762609..e44bcc9b1c 100644 --- a/python-squid/src/main/java/org/sonar/python/metrics/FileMetrics.java +++ b/python-squid/src/main/java/org/sonar/python/metrics/FileMetrics.java @@ -19,31 +19,30 @@ */ package org.sonar.python.metrics; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.PyStatement; +import com.sonar.sslr.api.AstNode; import java.util.ArrayList; import java.util.List; +import org.sonar.python.PythonVisitorContext; +import org.sonar.python.api.PythonGrammar; public class FileMetrics { private int numberOfStatements; private int numberOfClasses; - private int cyclomaticComplexity; + private final ComplexityVisitor complexityVisitor = new ComplexityVisitor(); private final CognitiveComplexityVisitor cognitiveComplexityVisitor = new CognitiveComplexityVisitor(null); - private final MetricsVisitor metricsVisitor; + private final FileLinesVisitor fileLinesVisitor; private List functionComplexities = new ArrayList<>(); - public FileMetrics(boolean ignoreHeaderComments, PyFile pyFile) { - numberOfStatements = PsiTreeUtil.findChildrenOfType(pyFile, PyStatement.class).size(); - numberOfClasses = PsiTreeUtil.findChildrenOfType(pyFile, PyClass.class).size(); - cyclomaticComplexity = ComplexityVisitor.complexity(pyFile); - pyFile.accept(cognitiveComplexityVisitor); - metricsVisitor = new MetricsVisitor(ignoreHeaderComments); - pyFile.accept(metricsVisitor); - for (PyFunction functionDef : PsiTreeUtil.findChildrenOfType(pyFile, PyFunction.class)) { + public FileMetrics(PythonVisitorContext context, boolean ignoreHeaderComments) { + AstNode rootTree = context.rootTree(); + numberOfStatements = rootTree.getDescendants(PythonGrammar.STATEMENT).size(); + numberOfClasses = rootTree.getDescendants(PythonGrammar.CLASSDEF).size(); + complexityVisitor.scanFile(context); + cognitiveComplexityVisitor.scanFile(context); + fileLinesVisitor = new FileLinesVisitor(ignoreHeaderComments); + fileLinesVisitor.scanFile(context); + for (AstNode functionDef : rootTree.getDescendants(PythonGrammar.FUNCDEF)) { functionComplexities.add(ComplexityVisitor.complexity(functionDef)); } } @@ -61,7 +60,7 @@ public int numberOfClasses() { } public int complexity() { - return cyclomaticComplexity; + return complexityVisitor.getComplexity(); } public int cognitiveComplexity() { @@ -72,8 +71,8 @@ public List functionComplexities() { return functionComplexities; } - public MetricsVisitor metricsVisitor() { - return metricsVisitor; + public FileLinesVisitor fileLinesVisitor() { + return fileLinesVisitor; } } diff --git a/python-squid/src/main/java/org/sonar/python/metrics/MetricsVisitor.java b/python-squid/src/main/java/org/sonar/python/metrics/MetricsVisitor.java deleted file mode 100644 index 1202c8068d..0000000000 --- a/python-squid/src/main/java/org/sonar/python/metrics/MetricsVisitor.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python.metrics; - -import com.intellij.psi.PsiComment; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiWhiteSpace; -import com.intellij.psi.impl.source.tree.LeafPsiElement; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.PyExceptPart; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyExpressionStatement; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyIfPart; -import com.jetbrains.python.psi.PyIfStatement; -import com.jetbrains.python.psi.PyRecursiveElementVisitor; -import com.jetbrains.python.psi.PyStatement; -import com.jetbrains.python.psi.PyStringLiteralExpression; -import com.jetbrains.python.psi.PyTryExceptStatement; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import org.sonar.python.frontend.PythonTokenLocation; - -public class MetricsVisitor extends PyRecursiveElementVisitor { - - private final boolean ignoreHeaderComments; - private Set linesOfCode = new HashSet<>(); - private Set executableLines = new HashSet<>(); - private Set linesOfComments = new HashSet<>(); - private Set noSonar = new HashSet<>(); - private boolean firstNonCommentSeen = false; - - public MetricsVisitor(boolean ignoreHeaderComments) { - this.ignoreHeaderComments = ignoreHeaderComments; - } - - @Override - public void visitElement(PsiElement element) { - // track the first meaningful element which is not a comment to handle ignoreHeaderComments - if (!((element instanceof PyFile) || (element instanceof PsiComment) || (element instanceof PsiWhiteSpace))) { - firstNonCommentSeen = true; - } - - // track lines of code - if (element instanceof LeafPsiElement - && !(element instanceof PsiWhiteSpace) - && !(element instanceof PsiComment) - && element.getNode().getElementType() != PyTokenTypes.DOCSTRING) { - PythonTokenLocation location = new PythonTokenLocation(element); - for (int line = location.startLine(); line <= location.endLine(); line++) { - linesOfCode.add(line); - } - } - - // track executable lines of code - if (element instanceof PyStatement) { - handlePyStatement((PyStatement) element); - } - - // track lines of comments and no_sonar lines - if (element instanceof PsiComment && (firstNonCommentSeen || !ignoreHeaderComments)) { - handleComment(((PsiComment) element)); - } - - super.visitElement(element); - } - - private void handlePyStatement(PyStatement element) { - if (!isDocString(element)) { - executableLines.add(new PythonTokenLocation(element).startLine()); - } - if (element instanceof PyIfStatement) { - for (PyIfPart pyIfPart : ((PyIfStatement) element).getElifParts()) { - executableLines.add(new PythonTokenLocation(pyIfPart).startLine()); - } - } else if (element instanceof PyTryExceptStatement) { - for (PyExceptPart pyExceptPart : ((PyTryExceptStatement) element).getExceptParts()) { - executableLines.add(new PythonTokenLocation(pyExceptPart).startLine()); - } - } - } - - private void handleComment(PsiComment comment) { - String[] commentLines = getCommentContent(comment.getText()) - .split("(\r)?\n|\r", -1); - int line = new PythonTokenLocation(comment).startLine(); - - for (String commentLine : commentLines) { - if (commentLine.contains("NOSONAR")) { - noSonar.add(line); - } else if (!isBlankComment(commentLine) && !noSonar.contains(line)) { - linesOfComments.add(line); - } - line++; - } - } - - @Override - public void visitPyStringLiteralExpression(PyStringLiteralExpression node) { - if (node.isDocString()) { - PythonTokenLocation location = new PythonTokenLocation(node); - for (int line = location.startLine(); line <= location.endLine(); line++) { - linesOfComments.add(line); - } - } - super.visitPyStringLiteralExpression(node); - } - - private static boolean isDocString(PyStatement element) { - if (element instanceof PyExpressionStatement) { - PyExpression expression = ((PyExpressionStatement) element).getExpression(); - return expression instanceof PyStringLiteralExpression - && ((PyStringLiteralExpression) expression).isDocString(); - } - return false; - } - - private static boolean isBlankComment(String line) { - for (int i = 0; i < line.length(); i++) { - if (Character.isLetterOrDigit(line.charAt(i))) { - return false; - } - } - return true; - } - - private static String getCommentContent(String comment) { - // Comment always starts with "#" - return comment.substring(comment.indexOf('#')); - } - - public Set getLinesWithNoSonar() { - return Collections.unmodifiableSet(noSonar); - } - - public Set getExecutableLines() { - return Collections.unmodifiableSet(executableLines); - } - - public Set getLinesOfCode() { - return Collections.unmodifiableSet(linesOfCode); - } - - public int getCommentLineCount() { - return linesOfComments.size(); - } - -} diff --git a/python-squid/src/test/java/org/sonar/python/MetricsVisitorTest.java b/python-squid/src/test/java/org/sonar/python/FileLinesVisitorTest.java similarity index 63% rename from python-squid/src/test/java/org/sonar/python/MetricsVisitorTest.java rename to python-squid/src/test/java/org/sonar/python/FileLinesVisitorTest.java index dc72495406..b0041d1638 100644 --- a/python-squid/src/test/java/org/sonar/python/MetricsVisitorTest.java +++ b/python-squid/src/test/java/org/sonar/python/FileLinesVisitorTest.java @@ -21,18 +21,19 @@ import java.io.File; import org.junit.Test; -import org.sonar.python.frontend.PythonParser; -import org.sonar.python.metrics.MetricsVisitor; +import org.sonar.python.metrics.FileLinesVisitor; import static org.assertj.core.api.Assertions.assertThat; -public class MetricsVisitorTest { +public class FileLinesVisitorTest { private static final File BASE_DIR = new File("src/test/resources/metrics"); @Test public void test() { - MetricsVisitor visitor = metricsVisitor(new File(BASE_DIR, "file_lines.py"), false); + FileLinesVisitor visitor = new FileLinesVisitor(false); + + TestPythonVisitorRunner.scanFile(new File(BASE_DIR, "file_lines.py"), visitor); assertThat(visitor.getLinesOfCode()).hasSize(12); assertThat(visitor.getLinesOfCode()).containsOnly(2, 4, 7, 8, 9, 10, 11, 12, 14, 15, 17, 21); @@ -44,28 +45,19 @@ public void test() { @Test public void test_ignoreHeaderComments() { - // do not ignoreHeaderComments - MetricsVisitor visitor = metricsVisitor(new File(BASE_DIR, "file_lines_header_comments.py"), false); - assertThat(visitor.getLinesOfCode()).containsOnly(6, 8); - assertThat(visitor.getCommentLineCount()).isEqualTo(4); + FileLinesVisitor visitor = new FileLinesVisitor(true); + + TestPythonVisitorRunner.scanFile(new File(BASE_DIR, "file_lines_header_comments.py"), visitor); - // ignoreHeaderComments - visitor = metricsVisitor(new File(BASE_DIR, "file_lines_header_comments.py"), true); - assertThat(visitor.getLinesOfCode()).containsOnly(6, 8); + assertThat(visitor.getLinesOfCode()).containsOnly(2, 4); assertThat(visitor.getCommentLineCount()).isEqualTo(1); } @Test public void executable_lines() { - MetricsVisitor visitor = metricsVisitor(new File(BASE_DIR, "executable_lines.py"), false); - + FileLinesVisitor visitor = new FileLinesVisitor(false); + TestPythonVisitorRunner.scanFile(new File(BASE_DIR, "executable_lines.py"), visitor); assertThat(visitor.getExecutableLines()).containsOnly(1, 2, 4, 7, 11, 13, 14, 15, 16, 18, 20, 21, 22, 23, 25, 27, 28, 29); } - private static MetricsVisitor metricsVisitor(File file, boolean ignoreHeaderComments) { - MetricsVisitor visitor = new MetricsVisitor(ignoreHeaderComments); - PythonParser.parse(file).accept(visitor); - return visitor; - } - } diff --git a/python-squid/src/test/java/org/sonar/python/PythonCheckTest.java b/python-squid/src/test/java/org/sonar/python/PythonCheckTest.java index 656360539f..2c380a5ed5 100644 --- a/python-squid/src/test/java/org/sonar/python/PythonCheckTest.java +++ b/python-squid/src/test/java/org/sonar/python/PythonCheckTest.java @@ -20,19 +20,14 @@ package org.sonar.python; import com.google.common.collect.ImmutableSet; -import com.intellij.lang.ASTNode; -import com.jetbrains.python.PyStubElementTypes; -import com.jetbrains.python.psi.PyFunction; import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.AstNodeType; import java.io.File; -import java.util.Collections; import java.util.List; import java.util.Set; import org.junit.Test; import org.sonar.python.PythonCheck.PreciseIssue; import org.sonar.python.api.PythonGrammar; -import org.sonar.python.frontend.PythonParser; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +39,6 @@ public class PythonCheckTest { private static List scanFileForIssues(File file, PythonCheck check) { PythonVisitorContext context = TestPythonVisitorRunner.createContext(file); check.scanFile(context); - SubscriptionVisitor.analyze(Collections.singletonList(check), context, PythonParser.parse(file)); return context.getIssues(); } @@ -56,19 +50,11 @@ public void visitNode(AstNode astNode) { AstNode funcName = astNode.getFirstChild(PythonGrammar.FUNCNAME); addIssue(funcName, funcName.getTokenValue()); } - - @Override - public void initialize(Context context) { - context.registerSyntaxNodeConsumer(PyStubElementTypes.FUNCTION_DECLARATION, ctx -> { - ASTNode nameNode = ((PyFunction) ctx.syntaxNode()).getNameNode(); - ctx.addIssue(nameNode.getPsi(), nameNode.getText()); - }); - } }; List issues = scanFileForIssues(FILE, check); - assertThat(issues).hasSize(4); + assertThat(issues).hasSize(2); PreciseIssue firstIssue = issues.get(0); assertThat(firstIssue.cost()).isNull(); @@ -81,21 +67,6 @@ public void initialize(Context context) { assertThat(primaryLocation.endLine()).isEqualTo(1); assertThat(primaryLocation.startLineOffset()).isEqualTo(4); assertThat(primaryLocation.endLineOffset()).isEqualTo(9); - - PreciseIssue issue = issues.get(1); - assertThat(issue.primaryLocation().message()).isEqualTo("method"); - - issue = issues.get(2); - assertThat(issue.primaryLocation().message()).isEqualTo("hello"); - primaryLocation = issue.primaryLocation(); - assertThat(primaryLocation.message()).isEqualTo("hello"); - assertThat(primaryLocation.startLine()).isEqualTo(1); - assertThat(primaryLocation.endLine()).isEqualTo(1); - assertThat(primaryLocation.startLineOffset()).isEqualTo(4); - assertThat(primaryLocation.endLineOffset()).isEqualTo(9); - - issue = issues.get(3); - assertThat(issue.primaryLocation().message()).isEqualTo("method"); } @Test diff --git a/python-squid/src/test/java/org/sonar/python/metrics/CognitiveComplexityVisitorTest.java b/python-squid/src/test/java/org/sonar/python/metrics/CognitiveComplexityVisitorTest.java index 59b2a22971..c4523dda66 100644 --- a/python-squid/src/test/java/org/sonar/python/metrics/CognitiveComplexityVisitorTest.java +++ b/python-squid/src/test/java/org/sonar/python/metrics/CognitiveComplexityVisitorTest.java @@ -19,18 +19,20 @@ */ package org.sonar.python.metrics; -import com.intellij.psi.PsiComment; -import com.intellij.psi.PsiElement; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.PyRecursiveElementVisitor; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; import java.io.File; +import java.util.Arrays; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import org.junit.Test; -import org.sonar.python.frontend.PythonParser; -import org.sonar.python.frontend.PythonTokenLocation; +import org.sonar.python.PythonVisitor; +import org.sonar.python.TestPythonVisitorRunner; +import org.sonar.python.api.PythonGrammar; import static org.fest.assertions.Assertions.assertThat; @@ -40,31 +42,36 @@ public class CognitiveComplexityVisitorTest { public void file() { Map complexityByLine = new TreeMap<>(); CognitiveComplexityVisitor fileComplexityVisitor = new CognitiveComplexityVisitor( - (node, message) -> complexityByLine.merge(line(node), message, (a, b) -> a + " " + b)); + (node, message) -> complexityByLine.merge(node.getTokenLine(), message, (a, b) -> a + " " + b)); StringBuilder comments = new StringBuilder(); - PyRecursiveElementVisitor functionAndCommentVisitor = new PyRecursiveElementVisitor() { + PythonVisitor functionAndCommentVisitor = new PythonVisitor() { + @Override + public Set subscribedKinds() { + return new HashSet<>(Arrays.asList(PythonGrammar.FUNCDEF)); + } @Override - public void visitPyFunction(PyFunction node) { - if (PsiTreeUtil.getParentOfType(node, PyFunction.class) == null) { + public void visitNode(AstNode node) { + if (!node.hasAncestor(PythonGrammar.FUNCDEF)) { int functionComplexity = CognitiveComplexityVisitor.complexity(node, null); - complexityByLine.merge(line(node), "=" + functionComplexity, (a, b) -> a + " " + b); + complexityByLine.merge(node.getTokenLine(), "=" + functionComplexity, (a, b) -> a + " " + b); } - super.visitPyFunction(node); } @Override - public void visitComment(PsiComment comment) { - String content = comment.getText().substring(1).trim(); - if (content.startsWith("=") || content.startsWith("+")) { - comments.append("line " + line(comment) + " " + content + "\n"); + public void visitToken(Token token) { + for (Trivia trivia : token.getTrivia()) { + if (trivia.isComment()) { + String content = trivia.getToken().getValue().substring(1).trim(); + if (content.startsWith("=") || content.startsWith("+")) { + comments.append("line " + trivia.getToken().getLine() + " " + content + "\n"); + } + } } } }; - PyFile pyFile = PythonParser.parse(new File("src/test/resources/metrics/cognitive-complexities.py")); - pyFile.accept(fileComplexityVisitor); - pyFile.accept(functionAndCommentVisitor); + TestPythonVisitorRunner.scanFile(new File("src/test/resources/metrics/cognitive-complexities.py"), fileComplexityVisitor, functionAndCommentVisitor); assertThat(fileComplexityVisitor.getComplexity()).isEqualTo(91); StringBuilder complexityReport = new StringBuilder(); @@ -72,8 +79,4 @@ public void visitComment(PsiComment comment) { assertThat(complexityReport.toString()).isEqualTo(comments.toString()); } - private static int line(PsiElement element) { - return new PythonTokenLocation(element).startLine(); - } - } diff --git a/python-squid/src/test/java/org/sonar/python/metrics/ComplexityVisitorTest.java b/python-squid/src/test/java/org/sonar/python/metrics/ComplexityVisitorTest.java index 6d4a2a9198..138462c3a5 100644 --- a/python-squid/src/test/java/org/sonar/python/metrics/ComplexityVisitorTest.java +++ b/python-squid/src/test/java/org/sonar/python/metrics/ComplexityVisitorTest.java @@ -19,21 +19,27 @@ */ package org.sonar.python.metrics; -import com.jetbrains.python.psi.PyFile; +import com.sonar.sslr.api.Grammar; +import com.sonar.sslr.impl.Parser; import java.io.File; +import java.nio.charset.StandardCharsets; import org.junit.Test; -import org.sonar.python.frontend.PythonParser; +import org.sonar.python.PythonConfiguration; +import org.sonar.python.TestPythonVisitorRunner; +import org.sonar.python.parser.PythonParser; import static org.fest.assertions.Assertions.assertThat; public class ComplexityVisitorTest { - private PythonParser parser = new PythonParser(); + private ComplexityVisitor visitor = new ComplexityVisitor(); + + private Parser parser = PythonParser.create(new PythonConfiguration(StandardCharsets.UTF_8)); @Test public void file() { - PyFile pyFile = PythonParser.parse(new File("src/test/resources/metrics/complexity.py")); - assertThat(ComplexityVisitor.complexity(pyFile)).isEqualTo(7); + TestPythonVisitorRunner.scanFile(new File("src/test/resources/metrics/complexity.py"), visitor); + assertThat(visitor.getComplexity()).isEqualTo(7); } @Test @@ -44,8 +50,6 @@ public void pass_keyword() { @Test public void if_keyword() { assertThat(complexity("if x: pass")).isEqualTo(1); - assertThat(complexity("x = a if condition else b")).isEqualTo(1); - assertThat(complexity("foo([x for x in lst if bar(x)])")).isEqualTo(1); } @Test diff --git a/python-squid/src/test/java/org/sonar/python/metrics/FileMetricsTest.java b/python-squid/src/test/java/org/sonar/python/metrics/FileMetricsTest.java index df2fa099bd..f542ddf838 100644 --- a/python-squid/src/test/java/org/sonar/python/metrics/FileMetricsTest.java +++ b/python-squid/src/test/java/org/sonar/python/metrics/FileMetricsTest.java @@ -21,7 +21,7 @@ import java.io.File; import org.junit.Test; -import org.sonar.python.frontend.PythonParser; +import org.sonar.python.TestPythonVisitorRunner; import static org.fest.assertions.Assertions.assertThat; @@ -61,7 +61,7 @@ public void function_complexities() { private static FileMetrics metrics(String fileName) { File baseDir = new File("src/test/resources/metrics/"); File file = new File(baseDir, fileName); - return new FileMetrics(true, PythonParser.parse(file)); + return new FileMetrics(TestPythonVisitorRunner.createContext(file), true); } } diff --git a/python-squid/src/test/resources/metrics/file_lines_header_comments.py b/python-squid/src/test/resources/metrics/file_lines_header_comments.py index bb68c7fc4a..fa1efadc9d 100644 --- a/python-squid/src/test/resources/metrics/file_lines_header_comments.py +++ b/python-squid/src/test/resources/metrics/file_lines_header_comments.py @@ -1,8 +1,4 @@ # comment (in the header) -# comment (still in header) - -# comment (still in header) - if 2 > 1: print "a" # comment diff --git a/sonar-python-plugin/pom.xml b/sonar-python-plugin/pom.xml index 3fadbd6279..05d2b2689c 100644 --- a/sonar-python-plugin/pom.xml +++ b/sonar-python-plugin/pom.xml @@ -24,11 +24,6 @@ - - org.sonarsource.python - python-frontend - ${project.version} - org.sonarsource.sonarqube sonar-plugin-api @@ -119,809 +114,8 @@ META-INF/*.SF LICENSE* NOTICE* - **/*.png - **/*.ico - - - com.jetbrains.pycharm:extensions - - com/intellij/openapi/extensions/AreaInstance.class - com/intellij/openapi/extensions/AreaListener.class - com/intellij/openapi/extensions/AreaPicoContainer.class - com/intellij/openapi/extensions/BaseExtensionPointName.class - com/intellij/openapi/extensions/EPAvailabilityListenerExtension.class - com/intellij/openapi/extensions/ExtensionNotApplicableException.class - com/intellij/openapi/extensions/ExtensionPoint$Kind.class - com/intellij/openapi/extensions/ExtensionPoint.class - com/intellij/openapi/extensions/ExtensionPointAndAreaListener.class - com/intellij/openapi/extensions/ExtensionPointAvailabilityListener.class - com/intellij/openapi/extensions/ExtensionPointListener.class - com/intellij/openapi/extensions/ExtensionPointName.class - com/intellij/openapi/extensions/Extensions$AreaClassConfiguration.class - com/intellij/openapi/extensions/Extensions.class - com/intellij/openapi/extensions/ExtensionsArea.class - com/intellij/openapi/extensions/LoadingOrder$Orderable.class - com/intellij/openapi/extensions/LoadingOrder.class - com/intellij/openapi/extensions/PluginAware.class - com/intellij/openapi/extensions/PluginDescriptor.class - com/intellij/openapi/extensions/SortingException.class - com/intellij/openapi/extensions/impl/BeanExtensionPoint.class - com/intellij/openapi/extensions/impl/ExtensionComponentAdapter.class - com/intellij/openapi/extensions/impl/ExtensionPointImpl$1.class - com/intellij/openapi/extensions/impl/ExtensionPointImpl$ObjectComponentAdapter.class - com/intellij/openapi/extensions/impl/ExtensionPointImpl.class - com/intellij/openapi/extensions/impl/ExtensionsAreaImpl$1.class - com/intellij/openapi/extensions/impl/ExtensionsAreaImpl$3.class - com/intellij/openapi/extensions/impl/ExtensionsAreaImpl.class - com/intellij/openapi/extensions/impl/InterfaceExtensionPoint.class - com/intellij/openapi/extensions/impl/PicoPluginExtensionInitializationException.class - com/intellij/openapi/extensions/impl/UndefinedPluginDescriptor.class - com/intellij/openapi/extensions/impl/XmlExtensionAdapter$ConstructorInjectionAdapter.class - com/intellij/openapi/extensions/impl/XmlExtensionAdapter$PicoComponentAdapter.class - com/intellij/openapi/extensions/impl/XmlExtensionAdapter.class - com/intellij/util/pico/AssignableToComponentAdapter.class - com/intellij/util/pico/CachingConstructorInjectionComponentAdapter.class - com/intellij/util/pico/DefaultPicoContainer$LinkedHashSetWrapper.class - com/intellij/util/pico/DefaultPicoContainer.class - - - - org.jetbrains.intellij.deps:trove4j - - gnu/trove/Equality.class - gnu/trove/HashFunctions.class - gnu/trove/PrimeFinder.class - gnu/trove/TByteArrayList.class - gnu/trove/TByteProcedure.class - gnu/trove/THash.class - gnu/trove/THashMap.class - gnu/trove/THashSet.class - gnu/trove/TIntArrayList.class - gnu/trove/TIntHash.class - gnu/trove/TIntHashSet.class - gnu/trove/TIntHashingStrategy.class - gnu/trove/TIntObjectHashMap.class - gnu/trove/TIntObjectProcedure.class - gnu/trove/TIntProcedure.class - gnu/trove/TIntStack.class - gnu/trove/TObjectCanonicalHashingStrategy.class - gnu/trove/TObjectHash$NULL.class - gnu/trove/TObjectHash.class - gnu/trove/TObjectHashingStrategy.class - gnu/trove/TObjectIdentityHashingStrategy.class - gnu/trove/TObjectObjectProcedure.class - gnu/trove/TObjectProcedure.class - gnu/trove/TPrimitiveHash.class - - - - org.jetbrains.kotlin:kotlin-stdlib - - kotlin/KotlinNullPointerException.class - kotlin/Pair.class - kotlin/TypeCastException.class - kotlin/UninitializedPropertyAccessException.class - kotlin/jvm/internal/Intrinsics.class - kotlin/text/StringsKt.class - kotlin/text/StringsKt__IndentKt.class - kotlin/text/StringsKt__RegexExtensionsJVMKt.class - kotlin/text/StringsKt__RegexExtensionsKt.class - kotlin/text/StringsKt__StringBuilderJVMKt.class - kotlin/text/StringsKt__StringBuilderKt.class - kotlin/text/StringsKt__StringNumberConversionsJVMKt.class - kotlin/text/StringsKt__StringNumberConversionsKt.class - kotlin/text/StringsKt__StringsJVMKt.class - kotlin/text/StringsKt__StringsKt.class - kotlin/text/StringsKt___StringsJvmKt.class - kotlin/text/StringsKt___StringsKt.class - - - - com.jetbrains.pycharm:platform-impl - - com/intellij/core/CoreASTFactory.class - com/intellij/core/CoreFileTypeRegistry.class - com/intellij/execution/KillableProcess.class - com/intellij/execution/console/BaseConsoleExecuteActionHandler.class - com/intellij/execution/console/ConsoleExecuteAction$ConsoleExecuteActionHandler.class - com/intellij/execution/console/ConsoleRootType.class - com/intellij/execution/console/LanguageConsoleView.class - com/intellij/execution/console/ProcessBackedConsoleExecuteActionHandler.class - com/intellij/execution/process/ColoredProcessHandler.class - com/intellij/execution/process/KillableColoredProcessHandler.class - com/intellij/execution/process/KillableProcessHandler.class - com/intellij/extapi/psi/ASTDelegatePsiElement.class - com/intellij/extapi/psi/PsiFileBase.class - com/intellij/extapi/psi/StubBasedPsiElementBase$1.class - com/intellij/extapi/psi/StubBasedPsiElementBase.class - com/intellij/ide/errorTreeView/NewErrorTreeViewPanel.class - com/intellij/lang/ASTFactory$DefaultFactoryHolder.class - com/intellij/lang/ASTFactory.class - com/intellij/lang/DefaultASTFactory.class - com/intellij/lang/LanguageASTFactory.class - com/intellij/lang/TokenWrapper.class - com/intellij/lang/WhitespacesBinders$1.class - com/intellij/lang/WhitespacesBinders$2.class - com/intellij/lang/WhitespacesBinders.class - com/intellij/lang/impl/MarkerOptionalData.class - com/intellij/lang/impl/MarkerPool.class - com/intellij/lang/impl/MarkerProduction.class - com/intellij/lang/impl/PsiBuilderFactoryImpl.class - com/intellij/lang/impl/PsiBuilderImpl$ErrorItem.class - com/intellij/lang/impl/PsiBuilderImpl$LazyParseableToken.class - com/intellij/lang/impl/PsiBuilderImpl$Node.class - com/intellij/lang/impl/PsiBuilderImpl$ProductionMarker.class - com/intellij/lang/impl/PsiBuilderImpl$RelativeTokenTextView.class - com/intellij/lang/impl/PsiBuilderImpl$RelativeTokenTypesView.class - com/intellij/lang/impl/PsiBuilderImpl$StartMarker.class - com/intellij/lang/impl/PsiBuilderImpl$Token.class - com/intellij/lang/impl/PsiBuilderImpl.class - com/intellij/lang/impl/TokenSequence$Builder.class - com/intellij/lang/impl/TokenSequence.class - com/intellij/mock/MockApplication.class - com/intellij/mock/MockComponentManager$1.class - com/intellij/mock/MockComponentManager.class - com/intellij/mock/MockFileDocumentManagerImpl.class - com/intellij/mock/MockProject$1.class - com/intellij/mock/MockProject.class - com/intellij/notebook/editor/BackedVirtualFile.class - com/intellij/openapi/application/impl/AnyModalityState.class - com/intellij/openapi/editor/ex/DocumentEx.class - com/intellij/openapi/editor/ex/LineIterator.class - com/intellij/openapi/editor/ex/MarkupIterator.class - com/intellij/openapi/editor/ex/PrioritizedDocumentListener$1.class - com/intellij/openapi/editor/ex/PrioritizedDocumentListener.class - com/intellij/openapi/editor/ex/PrioritizedInternalDocumentListener.class - com/intellij/openapi/editor/ex/RangeMarkerEx.class - com/intellij/openapi/editor/impl/DocumentImpl$1.class - com/intellij/openapi/editor/impl/DocumentImpl$UnexpectedBulkUpdateStateException.class - com/intellij/openapi/editor/impl/DocumentImpl.class - com/intellij/openapi/editor/impl/Interval.class - com/intellij/openapi/editor/impl/IntervalTree.class - com/intellij/openapi/editor/impl/IntervalTreeImpl$IntervalNode.class - com/intellij/openapi/editor/impl/IntervalTreeImpl$IntervalTreeGuide.class - com/intellij/openapi/editor/impl/IntervalTreeImpl.class - com/intellij/openapi/editor/impl/LineSet.class - com/intellij/openapi/editor/impl/LockFreeCOWSortedArray.class - com/intellij/openapi/editor/impl/MutableInterval.class - com/intellij/openapi/editor/impl/PersistentRangeMarker.class - com/intellij/openapi/editor/impl/RangeMarkerImpl.class - com/intellij/openapi/editor/impl/RangeMarkerTree$RMNode.class - com/intellij/openapi/editor/impl/RangeMarkerTree.class - com/intellij/openapi/editor/impl/RedBlackTree$Node.class - com/intellij/openapi/editor/impl/RedBlackTree.class - com/intellij/openapi/editor/impl/event/DocumentEventImpl.class - com/intellij/openapi/fileEditor/impl/LoadTextUtil$SevenBitCharset.class - com/intellij/openapi/fileEditor/impl/LoadTextUtil.class - com/intellij/openapi/fileTypes/PlainTextLanguage.class - com/intellij/openapi/progress/impl/CoreProgressManager$1.class - com/intellij/openapi/progress/impl/CoreProgressManager$2.class - com/intellij/openapi/progress/impl/CoreProgressManager$3.class - com/intellij/openapi/progress/impl/CoreProgressManager$CheckCanceledHook.class - com/intellij/openapi/progress/impl/CoreProgressManager.class - com/intellij/openapi/progress/impl/NonCancelableIndicator$1.class - com/intellij/openapi/progress/impl/NonCancelableIndicator.class - com/intellij/openapi/progress/impl/ProgressManagerImpl$1.class - com/intellij/openapi/progress/impl/ProgressManagerImpl.class - com/intellij/openapi/progress/util/PingProgress.class - com/intellij/openapi/wm/ex/ProgressIndicatorEx.class - com/intellij/psi/AbstractFileViewProvider$Content.class - com/intellij/psi/AbstractFileViewProvider$VirtualFileContent.class - com/intellij/psi/AbstractFileViewProvider.class - com/intellij/psi/SingleRootFileViewProvider.class - com/intellij/psi/impl/AnyPsiChangeListener.class - com/intellij/psi/impl/DebugUtil$1.class - com/intellij/psi/impl/DebugUtil$IncorrectTreeStructureException.class - com/intellij/psi/impl/DebugUtil$TreeToBuffer.class - com/intellij/psi/impl/DebugUtil.class - com/intellij/psi/impl/GeneratedMarkerVisitor.class - com/intellij/psi/impl/PsiElementBase.class - com/intellij/psi/impl/PsiFileEx.class - com/intellij/psi/impl/PsiFileFactoryImpl.class - com/intellij/psi/impl/PsiManagerEx.class - com/intellij/psi/impl/PsiManagerImpl.class - com/intellij/psi/impl/PsiTreeChangeEventImpl.class - com/intellij/psi/impl/file/PsiBinaryFileImpl.class - com/intellij/psi/impl/file/PsiLargeBinaryFileImpl.class - com/intellij/psi/impl/file/impl/FileManager.class - com/intellij/psi/impl/file/impl/FileManagerImpl$1.class - com/intellij/psi/impl/file/impl/FileManagerImpl$2.class - com/intellij/psi/impl/file/impl/FileManagerImpl.class - com/intellij/psi/impl/source/CharTableImpl$1.class - com/intellij/psi/impl/source/CharTableImpl$StringHashToCharSequencesMap.class - com/intellij/psi/impl/source/CharTableImpl.class - com/intellij/psi/impl/source/CodeFragmentElement.class - com/intellij/psi/impl/source/DummyHolderElement.class - com/intellij/psi/impl/source/FileTrees.class - com/intellij/psi/impl/source/PsiFileImpl.class - com/intellij/psi/impl/source/PsiFileWithStubSupport.class - com/intellij/psi/impl/source/SourceTreeToPsiMap.class - com/intellij/psi/impl/source/SpineRef.class - com/intellij/psi/impl/source/StubbedSpine.class - com/intellij/psi/impl/source/SubstrateRef$1.class - com/intellij/psi/impl/source/SubstrateRef$2.class - com/intellij/psi/impl/source/SubstrateRef$StubRef.class - com/intellij/psi/impl/source/SubstrateRef.class - com/intellij/psi/impl/source/tree/AstBufferUtil$BufferVisitor.class - com/intellij/psi/impl/source/tree/AstBufferUtil.class - com/intellij/psi/impl/source/tree/CompositeElement$1.class - com/intellij/psi/impl/source/tree/CompositeElement$2.class - com/intellij/psi/impl/source/tree/CompositeElement.class - com/intellij/psi/impl/source/tree/CompositePsiElement.class - com/intellij/psi/impl/source/tree/Factory.class - com/intellij/psi/impl/source/tree/FileElement$1.class - com/intellij/psi/impl/source/tree/FileElement.class - com/intellij/psi/impl/source/tree/ForeignLeafPsiElement.class - com/intellij/psi/impl/source/tree/LazyParseableElement$1.class - com/intellij/psi/impl/source/tree/LazyParseableElement$ChameleonLock.class - com/intellij/psi/impl/source/tree/LazyParseableElement.class - com/intellij/psi/impl/source/tree/LeafElement.class - com/intellij/psi/impl/source/tree/LeafPsiElement.class - com/intellij/psi/impl/source/tree/PsiCoreCommentImpl.class - com/intellij/psi/impl/source/tree/PsiErrorElementImpl.class - com/intellij/psi/impl/source/tree/PsiWhiteSpaceImpl.class - com/intellij/psi/impl/source/tree/RecursiveTreeElementWalkingVisitor$1.class - com/intellij/psi/impl/source/tree/RecursiveTreeElementWalkingVisitor$ASTTreeGuide.class - com/intellij/psi/impl/source/tree/RecursiveTreeElementWalkingVisitor.class - com/intellij/psi/impl/source/tree/SharedImplUtil.class - com/intellij/psi/impl/source/tree/TreeElement.class - com/intellij/psi/impl/source/tree/TreeElementVisitor.class - com/intellij/psi/impl/source/tree/TreeUtil$1.class - com/intellij/psi/impl/source/tree/TreeUtil$1MyVisitor.class - com/intellij/psi/impl/source/tree/TreeUtil$2.class - com/intellij/psi/impl/source/tree/TreeUtil$3.class - com/intellij/psi/impl/source/tree/TreeUtil$4.class - com/intellij/psi/impl/source/tree/TreeUtil.class - com/intellij/psi/stubs/ObjectStubTree.class - com/intellij/psi/stubs/StubTree.class - com/intellij/psi/text/BlockSupport$ReparsedSuccessfullyException.class - com/intellij/psi/text/BlockSupport.class - com/intellij/psi/tree/IStubFileElementType.class - com/intellij/util/PatchedWeakReference.class - com/intellij/util/indexing/IndexingDataKeys.class - org/intellij/lang/regexp/DefaultRegExpPropertiesProvider.class - org/intellij/lang/regexp/RegExpLanguageHost.class - org/intellij/lang/regexp/psi/RegExpGroup$Type.class - - - - com.jetbrains.pycharm:pycharm-pydev - - com/jetbrains/python/console/pydev/AbstractConsoleCommunication.class - com/jetbrains/python/console/pydev/ConsoleCommunication.class - com/jetbrains/python/console/pydev/ConsoleCommunicationListener.class - com/jetbrains/python/debugger/PyFrameAccessor.class - - - - com.jetbrains.pycharm:util - - com/intellij/BundleBase.class - com/intellij/CommonBundle.class - com/intellij/Patches.class - com/intellij/execution/TaskExecutor.class - com/intellij/execution/process/BaseOSProcessHandler.class - com/intellij/execution/process/BaseProcessHandler.class - com/intellij/execution/process/ProcessHandler.class - com/intellij/execution/process/ProcessListener.class - com/intellij/openapi/Disposable.class - com/intellij/openapi/application/AccessToken.class - com/intellij/openapi/diagnostic/Attachment.class - com/intellij/openapi/diagnostic/ControlFlowException.class - com/intellij/openapi/diagnostic/DefaultLogger.class - com/intellij/openapi/diagnostic/ExceptionWithAttachments.class - com/intellij/openapi/diagnostic/Logger$DefaultFactory.class - com/intellij/openapi/diagnostic/Logger$Factory.class - com/intellij/openapi/diagnostic/Logger.class - com/intellij/openapi/diagnostic/RuntimeExceptionWithAttachments.class - com/intellij/openapi/progress/ProcessCanceledException.class - com/intellij/openapi/util/AtomicNotNullLazyValue$1.class - com/intellij/openapi/util/AtomicNotNullLazyValue.class - com/intellij/openapi/util/Comparing.class - com/intellij/openapi/util/Condition$1.class - com/intellij/openapi/util/Condition$2.class - com/intellij/openapi/util/Condition$3.class - com/intellij/openapi/util/Condition.class - com/intellij/openapi/util/Conditions$1.class - com/intellij/openapi/util/Conditions$8.class - com/intellij/openapi/util/Conditions.class - com/intellij/openapi/util/Disposer$1.class - com/intellij/openapi/util/Disposer$2.class - com/intellij/openapi/util/Disposer.class - com/intellij/openapi/util/EmptyRunnable.class - com/intellij/openapi/util/Getter.class - com/intellij/openapi/util/Key.class - com/intellij/openapi/util/KeyWithDefaultValue.class - com/intellij/openapi/util/LowMemoryWatcher$1.class - com/intellij/openapi/util/LowMemoryWatcher$LowMemoryWatcherType.class - com/intellij/openapi/util/LowMemoryWatcher.class - com/intellij/openapi/util/NotNullLazyKey.class - com/intellij/openapi/util/NotNullLazyValue$1.class - com/intellij/openapi/util/NotNullLazyValue$2.class - com/intellij/openapi/util/NotNullLazyValue.class - com/intellij/openapi/util/Pair.class - com/intellij/openapi/util/RecursionGuard$StackStamp.class - com/intellij/openapi/util/RecursionGuard.class - com/intellij/openapi/util/RecursionManager$1.class - com/intellij/openapi/util/RecursionManager$2.class - com/intellij/openapi/util/RecursionManager$CalculationStack.class - com/intellij/openapi/util/RecursionManager.class - com/intellij/openapi/util/Segment.class - com/intellij/openapi/util/StackOverflowPreventedException.class - com/intellij/openapi/util/StaticGetter.class - com/intellij/openapi/util/SystemInfo.class - com/intellij/openapi/util/SystemInfoRt.class - com/intellij/openapi/util/TextRange.class - com/intellij/openapi/util/UnprotectedUserDataHolder.class - com/intellij/openapi/util/UserDataHolder.class - com/intellij/openapi/util/UserDataHolderBase.class - com/intellij/openapi/util/UserDataHolderEx.class - com/intellij/openapi/util/UserDataHolderUnprotected.class - com/intellij/openapi/util/io/FileTooBigException.class - com/intellij/openapi/util/io/FileUtil$1.class - com/intellij/openapi/util/io/FileUtil$2.class - com/intellij/openapi/util/io/FileUtil.class - com/intellij/openapi/util/io/FileUtilRt$1.class - com/intellij/openapi/util/io/FileUtilRt$2.class - com/intellij/openapi/util/io/FileUtilRt$3.class - com/intellij/openapi/util/io/FileUtilRt$RepeatableIOOperation.class - com/intellij/openapi/util/io/FileUtilRt$SymlinkResolver.class - com/intellij/openapi/util/io/FileUtilRt.class - com/intellij/openapi/util/io/PathExecLazyValue.class - com/intellij/openapi/util/objectTree/ObjectNode.class - com/intellij/openapi/util/objectTree/ObjectTree.class - com/intellij/openapi/util/objectTree/ObjectTreeAction.class - com/intellij/openapi/util/registry/Registry.class - com/intellij/openapi/util/registry/RegistryValue.class - com/intellij/openapi/util/text/CharSequenceWithStringHash.class - com/intellij/openapi/util/text/LineTokenizer.class - com/intellij/openapi/util/text/StringUtil$MyHtml2Text.class - com/intellij/openapi/util/text/StringUtil.class - com/intellij/openapi/util/text/StringUtilRt.class - com/intellij/openapi/vfs/CharsetToolkit.class - com/intellij/reference/SoftReference.class - com/intellij/util/ArrayFactory.class - com/intellij/util/ArrayUtil.class - com/intellij/util/ArrayUtilRt.class - com/intellij/util/BitUtil.class - com/intellij/util/ConcurrencyUtil.class - com/intellij/util/EventDispatcher$1.class - com/intellij/util/EventDispatcher$2.class - com/intellij/util/EventDispatcher.class - com/intellij/util/Function$1.class - com/intellij/util/Function$2.class - com/intellij/util/Function$Mono.class - com/intellij/util/Function.class - com/intellij/util/Functions$2.class - com/intellij/util/Functions$4.class - com/intellij/util/Functions$5.class - com/intellij/util/Functions$6.class - com/intellij/util/Functions$7.class - com/intellij/util/Functions.class - com/intellij/util/IncorrectOperationException.class - com/intellij/util/IntIntFunction.class - com/intellij/util/LocalTimeCounter.class - com/intellij/util/NotNullFunction.class - com/intellij/util/NotNullizer.class - com/intellij/util/NullableFunction$1.class - com/intellij/util/NullableFunction.class - com/intellij/util/ObjectUtils$Sentinel.class - com/intellij/util/ObjectUtils.class - com/intellij/util/Processor.class - com/intellij/util/ReflectionUtil.class - com/intellij/util/SmartFMap.class - com/intellij/util/SmartList$SingletonIterator.class - com/intellij/util/SmartList.class - com/intellij/util/ThrowableRunnable.class - com/intellij/util/WalkingState$TreeGuide.class - com/intellij/util/WalkingState.class - com/intellij/util/concurrency/AtomicFieldUpdater.class - com/intellij/util/containers/*.class - com/intellij/util/diff/DiffTreeChangeBuilder.class - com/intellij/util/diff/FlyweightCapableTreeStructure.class - com/intellij/util/diff/ShallowNodeComparator.class - com/intellij/util/graph/InboundSemiGraph.class - com/intellij/util/io/storage/HeavyProcessLatch$1.class - com/intellij/util/io/storage/HeavyProcessLatch$HeavyProcessListener.class - com/intellij/util/io/storage/HeavyProcessLatch.class - com/intellij/util/keyFMap/ArrayBackedFMap.class - com/intellij/util/keyFMap/EmptyFMap.class - com/intellij/util/keyFMap/KeyFMap.class - com/intellij/util/keyFMap/OneElementFMap.class - com/intellij/util/keyFMap/PairElementsFMap.class - com/intellij/util/lang/JavaVersion.class - com/intellij/util/messages/MessageBus.class - com/intellij/util/messages/MessageBusConnection.class - com/intellij/util/messages/MessageBusFactory$Impl$1.class - com/intellij/util/messages/MessageBusFactory$Impl.class - com/intellij/util/messages/MessageBusFactory.class - com/intellij/util/messages/Topic$BroadcastDirection.class - com/intellij/util/messages/Topic.class - com/intellij/util/messages/impl/MessageBusConnectionImpl.class - com/intellij/util/messages/impl/MessageBusImpl$RootBus.class - com/intellij/util/messages/impl/MessageBusImpl.class - com/intellij/util/text/ByteArrayCharSequence.class - com/intellij/util/text/CaseInsensitiveStringHashingStrategy.class - com/intellij/util/text/CharArrayCharSequence.class - com/intellij/util/text/CharArrayExternalizable.class - com/intellij/util/text/CharArrayUtil.class - com/intellij/util/text/CharSequenceBackedByArray.class - com/intellij/util/text/CharSequenceReader.class - com/intellij/util/text/CharSequenceSubSequence.class - com/intellij/util/text/FilePathHashingStrategy.class - com/intellij/util/text/ImmutableCharSequence.class - com/intellij/util/text/ImmutableText$CompositeNode.class - com/intellij/util/text/ImmutableText$InnerLeaf.class - com/intellij/util/text/ImmutableText$Leaf8BitNode.class - com/intellij/util/text/ImmutableText$LeafNode.class - com/intellij/util/text/ImmutableText$Node.class - com/intellij/util/text/ImmutableText$WideLeafNode.class - com/intellij/util/text/ImmutableText.class - com/intellij/util/text/StringFactory.class - com/intellij/util/text/UnsyncCharArrayReader.class - misc/registry.properties - - - - com.jetbrains.pycharm:resources_en - - com/jetbrains/python/PyBundle.properties - - - - com.jetbrains.pycharm:openapi - - - - - com.jetbrains.pycharm:platform-api - - com/intellij/codeInsight/completion/InsertHandler.class - com/intellij/codeInsight/lookup/LookupElement.class - com/intellij/codeInsight/lookup/LookupElementBuilder.class - com/intellij/diagnostic/ImplementationConflictException.class - com/intellij/diagnostic/PluginException.class - com/intellij/execution/ExecutionException.class - com/intellij/execution/filters/Filter.class - com/intellij/execution/process/AnsiEscapeDecoder$ColoredTextAcceptor.class - com/intellij/execution/process/OSProcessHandler.class - com/intellij/execution/ui/ConsoleView.class - com/intellij/execution/ui/ExecutionConsole.class - com/intellij/ide/CopyProvider.class - com/intellij/ide/OccurenceNavigator.class - com/intellij/ide/scratch/RootType.class - com/intellij/injected/editor/VirtualFileWindow.class - com/intellij/lang/ASTNode.class - com/intellij/lang/FCTSBackedLighterAST.class - com/intellij/lang/FileASTNode.class - com/intellij/lang/ITokenTypeRemapper.class - com/intellij/lang/Language$1.class - com/intellij/lang/Language.class - com/intellij/lang/LanguageExtension.class - com/intellij/lang/LanguageParserDefinitions.class - com/intellij/lang/LanguageUtil.class - com/intellij/lang/LighterAST.class - com/intellij/lang/LighterASTNode.class - com/intellij/lang/LighterLazyParseableNode.class - com/intellij/lang/MetaLanguage.class - com/intellij/lang/ParserDefinition.class - com/intellij/lang/PsiBuilder$Marker.class - com/intellij/lang/PsiBuilder.class - com/intellij/lang/PsiBuilderFactory.class - com/intellij/lang/PsiParser.class - com/intellij/lang/TreeBackedLighterAST.class - com/intellij/lang/WhitespacesAndCommentsBinder$RecursiveBinder.class - com/intellij/lang/WhitespacesAndCommentsBinder$TokenTextGetter.class - com/intellij/lang/WhitespacesAndCommentsBinder.class - com/intellij/lexer/DelegateLexer.class - com/intellij/lexer/FlexAdapter.class - com/intellij/lexer/FlexLexer.class - com/intellij/lexer/Lexer.class - com/intellij/lexer/LexerBase.class - com/intellij/lexer/LexerPosition.class - com/intellij/lexer/MergeFunction.class - com/intellij/lexer/MergingLexerAdapter$MyMergeFunction.class - com/intellij/lexer/MergingLexerAdapter.class - com/intellij/lexer/MergingLexerAdapterBase.class - com/intellij/model/SymbolReference.class - com/intellij/navigation/ColoredItemPresentation.class - com/intellij/navigation/ItemPresentation.class - com/intellij/navigation/NavigationItem.class - com/intellij/openapi/actionSystem/ActionGroup.class - com/intellij/openapi/actionSystem/AnAction.class - com/intellij/openapi/actionSystem/DataProvider.class - com/intellij/openapi/actionSystem/DefaultActionGroup.class - com/intellij/openapi/actionSystem/ToggleAction.class - com/intellij/openapi/actionSystem/Toggleable.class - com/intellij/openapi/actionSystem/UpdateInBackground.class - com/intellij/openapi/application/Application.class - com/intellij/openapi/application/ApplicationManager$2.class - com/intellij/openapi/application/ApplicationManager.class - com/intellij/openapi/application/CachedSingletonsRegistry.class - com/intellij/openapi/application/ModalityState.class - com/intellij/openapi/components/ComponentManager.class - com/intellij/openapi/components/ServiceManager.class - com/intellij/openapi/editor/Document.class - com/intellij/openapi/editor/RangeMarker.class - com/intellij/openapi/editor/ReadOnlyFragmentModificationException.class - com/intellij/openapi/editor/ReadOnlyModificationException.class - com/intellij/openapi/editor/actionSystem/EditorAction.class - com/intellij/openapi/editor/event/DocumentEvent.class - com/intellij/openapi/editor/event/DocumentListener.class - com/intellij/openapi/fileEditor/FileDocumentManager.class - com/intellij/openapi/fileTypes/CharsetUtil.class - com/intellij/openapi/fileTypes/FileType.class - com/intellij/openapi/fileTypes/FileTypeRegistry.class - com/intellij/openapi/fileTypes/LanguageFileType.class - com/intellij/openapi/fileTypes/UnknownFileType.class - com/intellij/openapi/options/Scheme.class - com/intellij/openapi/progress/NonCancelableSection.class - com/intellij/openapi/progress/PerformInBackgroundOption.class - com/intellij/openapi/progress/ProgressIndicator.class - com/intellij/openapi/progress/ProgressIndicatorProvider.class - com/intellij/openapi/progress/ProgressManager.class - com/intellij/openapi/progress/Progressive.class - com/intellij/openapi/progress/StandardProgressIndicator.class - com/intellij/openapi/progress/Task$Backgroundable.class - com/intellij/openapi/progress/Task$Modal.class - com/intellij/openapi/progress/Task$WithResult.class - com/intellij/openapi/progress/Task.class - com/intellij/openapi/progress/TaskInfo.class - com/intellij/openapi/progress/WrappedProgressIndicator.class - com/intellij/openapi/project/DumbAware.class - com/intellij/openapi/project/DumbAwareAction.class - com/intellij/openapi/project/DumbService$DumbModeListener.class - com/intellij/openapi/project/DumbService.class - com/intellij/openapi/project/IndexNotReadyException.class - com/intellij/openapi/project/PossiblyDumbAware.class - com/intellij/openapi/project/Project.class - com/intellij/openapi/ui/ComponentContainer.class - com/intellij/openapi/ui/Queryable.class - com/intellij/openapi/util/Iconable.class - com/intellij/openapi/util/KeyedExtensionCollector$1.class - com/intellij/openapi/util/KeyedExtensionCollector$2.class - com/intellij/openapi/util/KeyedExtensionCollector.class - com/intellij/openapi/util/ModificationTracker.class - com/intellij/openapi/util/SimpleModificationTracker.class - com/intellij/openapi/vfs/DeprecatedVirtualFileSystem.class - com/intellij/openapi/vfs/InvalidVirtualFileAccessException.class - com/intellij/openapi/vfs/NonPhysicalFileSystem.class - com/intellij/openapi/vfs/PersistentFSConstants.class - com/intellij/openapi/vfs/SavingRequestor.class - com/intellij/openapi/vfs/VFileProperty.class - com/intellij/openapi/vfs/VirtualFile.class - com/intellij/openapi/vfs/VirtualFileCopyEvent.class - com/intellij/openapi/vfs/VirtualFileEvent.class - com/intellij/openapi/vfs/VirtualFileFilter$1.class - com/intellij/openapi/vfs/VirtualFileFilter$2.class - com/intellij/openapi/vfs/VirtualFileFilter.class - com/intellij/openapi/vfs/VirtualFileListener.class - com/intellij/openapi/vfs/VirtualFileManager.class - com/intellij/openapi/vfs/VirtualFileMoveEvent.class - com/intellij/openapi/vfs/VirtualFilePropertyEvent.class - com/intellij/openapi/vfs/VirtualFileSystem.class - com/intellij/openapi/vfs/VirtualFileVisitor.class - com/intellij/openapi/vfs/impl/BulkVirtualFileListenerAdapter.class - com/intellij/openapi/vfs/newvfs/BulkFileListener.class - com/intellij/pom/Navigatable.class - com/intellij/psi/CommonClassNames.class - com/intellij/psi/FileViewProvider.class - com/intellij/psi/LanguageFileViewProviders.class - com/intellij/psi/LanguageSubstitutors.class - com/intellij/psi/LiteralTextEscaper.class - com/intellij/psi/NavigatablePsiElement.class - com/intellij/psi/PsiBinaryFile.class - com/intellij/psi/PsiCheckedRenameElement.class - com/intellij/psi/PsiComment.class - com/intellij/psi/PsiCompiledElement.class - com/intellij/psi/PsiDirectory.class - com/intellij/psi/PsiElement.class - com/intellij/psi/PsiElementVisitor$1.class - com/intellij/psi/PsiElementVisitor.class - com/intellij/psi/PsiErrorElement.class - com/intellij/psi/PsiFile.class - com/intellij/psi/PsiFileFactory.class - com/intellij/psi/PsiFileSystemItem.class - com/intellij/psi/PsiInvalidElementAccessException.class - com/intellij/psi/PsiLanguageInjectionHost.class - com/intellij/psi/PsiLargeBinaryFile.class - com/intellij/psi/PsiLargeFile.class - com/intellij/psi/PsiLiteralValue.class - com/intellij/psi/PsiLock.class - com/intellij/psi/PsiManager.class - com/intellij/psi/PsiNameIdentifierOwner.class - com/intellij/psi/PsiNamedElement.class - com/intellij/psi/PsiPolyVariantReference.class - com/intellij/psi/PsiRecursiveElementWalkingVisitor.class - com/intellij/psi/PsiRecursiveVisitor.class - com/intellij/psi/PsiReference.class - com/intellij/psi/PsiTreeChangeEvent.class - com/intellij/psi/PsiWhiteSpace.class - com/intellij/psi/StubBasedPsiElement.class - com/intellij/psi/StubBuilder.class - com/intellij/psi/SyntaxTraverser$ASTApi.class - com/intellij/psi/SyntaxTraverser$Api$1.class - com/intellij/psi/SyntaxTraverser$Api$2.class - com/intellij/psi/SyntaxTraverser$Api$3.class - com/intellij/psi/SyntaxTraverser$Api.class - com/intellij/psi/SyntaxTraverser$ApiEx$1.class - com/intellij/psi/SyntaxTraverser$ApiEx.class - com/intellij/psi/SyntaxTraverser$FlyweightApi.class - com/intellij/psi/SyntaxTraverser$LighterASTApi.class - com/intellij/psi/SyntaxTraverser$PsiApi$1.class - com/intellij/psi/SyntaxTraverser$PsiApi.class - com/intellij/psi/SyntaxTraverser.class - com/intellij/psi/TokenType$1.class - com/intellij/psi/TokenType.class - com/intellij/psi/impl/ElementBase$1.class - com/intellij/psi/impl/ElementBase$2.class - com/intellij/psi/impl/ElementBase$ElementIconRequest.class - com/intellij/psi/impl/ElementBase.class - com/intellij/psi/scope/PsiScopeProcessor.class - com/intellij/psi/search/GlobalSearchScope.class - com/intellij/psi/search/LocalSearchScope.class - com/intellij/psi/search/ProjectAwareFileFilter.class - com/intellij/psi/search/PsiElementProcessor$CollectElements.class - com/intellij/psi/search/PsiElementProcessor$FindElement.class - com/intellij/psi/search/PsiElementProcessor.class - com/intellij/psi/search/SearchScope.class - com/intellij/psi/stubs/IStubElementType.class - com/intellij/psi/stubs/NamedStub.class - com/intellij/psi/stubs/ObjectStubSerializer.class - com/intellij/psi/stubs/PsiFileStub.class - com/intellij/psi/stubs/Stub.class - com/intellij/psi/stubs/StubElement.class - com/intellij/psi/stubs/StubSerializer.class - com/intellij/psi/tree/ICompositeElementType.class - com/intellij/psi/tree/ICustomParsingType.class - com/intellij/psi/tree/IElementType$Predicate.class - com/intellij/psi/tree/IElementType.class - com/intellij/psi/tree/IFileElementType.class - com/intellij/psi/tree/ILazyParseableElementType.class - com/intellij/psi/tree/ILazyParseableElementTypeBase.class - com/intellij/psi/tree/ILeafElementType.class - com/intellij/psi/tree/StubFileElementType.class - com/intellij/psi/tree/TokenSet.class - com/intellij/psi/util/CachedValueProvider.class - com/intellij/psi/util/PsiModificationTracker$SERVICE.class - com/intellij/psi/util/PsiModificationTracker.class - com/intellij/psi/util/PsiTreeUtil$1.class - com/intellij/psi/util/PsiTreeUtil$2.class - com/intellij/psi/util/PsiTreeUtil$3.class - com/intellij/psi/util/PsiTreeUtil$4.class - com/intellij/psi/util/PsiTreeUtil.class - com/intellij/psi/util/PsiUtilCore$NullPsiElement.class - com/intellij/psi/util/PsiUtilCore$NullPsiFile.class - com/intellij/psi/util/PsiUtilCore.class - com/intellij/testFramework/LightVirtualFile$1.class - com/intellij/testFramework/LightVirtualFile.class - com/intellij/testFramework/LightVirtualFileBase$MyVirtualFileSystem.class - com/intellij/testFramework/LightVirtualFileBase.class - com/intellij/testFramework/ReadOnlyLightVirtualFile.class - com/intellij/util/CharTable.class - com/intellij/util/KeyedLazyInstance.class - com/intellij/util/ui/ErrorTreeView.class - com/intellij/util/ui/MutableErrorTreeView.class - com/intellij/xdebugger/XDebugProcessStarter.class - - - - com.jetbrains.pycharm:pycharm - - com/jetbrains/NotNullPredicate.class - com/jetbrains/python/PyBundle.class - com/jetbrains/python/PyElementTypes.class - com/jetbrains/python/PyStubElementTypes.class - com/jetbrains/python/PyTokenTypes.class - com/jetbrains/python/PyTypeDeclarationStatementImpl.class - com/jetbrains/python/PythonDialectsTokenSetContributor.class - com/jetbrains/python/PythonDialectsTokenSetContributorBase.class - com/jetbrains/python/PythonDialectsTokenSetProvider.class - com/jetbrains/python/PythonFileType.class - com/jetbrains/python/PythonLanguage.class - com/jetbrains/python/PythonParserDefinition.class - com/jetbrains/python/PythonTokenSetContributor.class - com/jetbrains/python/codeInsight/controlflow/ScopeOwner.class - com/jetbrains/python/console/PyConsoleProcessHandler.class - com/jetbrains/python/console/PyConsoleRootType.class - com/jetbrains/python/console/PydevConsoleCommunication.class - com/jetbrains/python/console/PydevConsoleCommunicationServer.class - com/jetbrains/python/console/PydevConsoleExecuteActionHandler.class - com/jetbrains/python/console/PydevConsoleRunner.class - com/jetbrains/python/console/PydevConsoleRunnerFactory.class - com/jetbrains/python/console/PydevConsoleRunnerImpl$1.class - com/jetbrains/python/console/PydevConsoleRunnerImpl$1ConsoleSplitLineAction.class - com/jetbrains/python/console/PydevConsoleRunnerImpl$2.class - com/jetbrains/python/console/PydevConsoleRunnerImpl$6.class - com/jetbrains/python/console/PydevConsoleRunnerImpl$8.class - com/jetbrains/python/console/PydevConsoleRunnerImpl$ConnectDebuggerAction.class - com/jetbrains/python/console/PydevConsoleRunnerImpl$NewConsoleAction.class - com/jetbrains/python/console/PydevConsoleRunnerImpl$RestartAction.class - com/jetbrains/python/console/PydevConsoleRunnerImpl.class - com/jetbrains/python/console/PythonConsoleExecuteActionHandler.class - com/jetbrains/python/console/PythonConsoleRunnerFactory.class - com/jetbrains/python/console/parsing/PyConsoleParser.class - com/jetbrains/python/console/parsing/PythonConsoleLexer.class - com/jetbrains/python/debugger/PyVariableViewSettings$SimplifiedView.class - com/jetbrains/python/debugger/PyVariableViewSettings$VariablesPolicyGroup.class - com/jetbrains/python/inspections/PythonVisitorFilter.class - com/jetbrains/python/lexer/FlexLexerEx.class - com/jetbrains/python/lexer/PyLexerFStringHelper$FStringState.class - com/jetbrains/python/lexer/PyLexerFStringHelper$FragmentState.class - com/jetbrains/python/lexer/PyLexerFStringHelper.class - com/jetbrains/python/lexer/PythonIndentingLexer.class - com/jetbrains/python/lexer/PythonIndentingProcessor$PendingCommentToken.class - com/jetbrains/python/lexer/PythonIndentingProcessor$PendingToken.class - com/jetbrains/python/lexer/PythonIndentingProcessor.class - com/jetbrains/python/lexer/_PythonLexer.class - com/jetbrains/python/parsing/ExpressionParsing.class - com/jetbrains/python/parsing/FollowingCommentBinder.class - com/jetbrains/python/parsing/FunctionParsing.class - com/jetbrains/python/parsing/LeadingCommentsBinder.class - com/jetbrains/python/parsing/Parsing.class - com/jetbrains/python/parsing/ParsingContext.class - com/jetbrains/python/parsing/ParsingScope.class - com/jetbrains/python/parsing/PyParser.class - com/jetbrains/python/parsing/StatementParsing$FUTURE.class - com/jetbrains/python/parsing/StatementParsing$ImportTypes.class - com/jetbrains/python/parsing/StatementParsing$Phase.class - com/jetbrains/python/parsing/StatementParsing.class - com/jetbrains/python/psi/*.class - com/jetbrains/python/psi/impl/*.class - com/jetbrains/python/psi/impl/stubs/PyAnnotationElementType.class - com/jetbrains/python/psi/impl/stubs/PyClassElementType.class - com/jetbrains/python/psi/impl/stubs/PyCustomStub.class - com/jetbrains/python/psi/impl/stubs/PyCustomizableStubElementType.class - com/jetbrains/python/psi/impl/stubs/PyDecoratorCallElementType.class - com/jetbrains/python/psi/impl/stubs/PyDecoratorListElementType.class - com/jetbrains/python/psi/impl/stubs/PyExceptPartElementType.class - com/jetbrains/python/psi/impl/stubs/PyFromImportStatementElementType.class - com/jetbrains/python/psi/impl/stubs/PyFunctionElementType.class - com/jetbrains/python/psi/impl/stubs/PyImportElementElementType.class - com/jetbrains/python/psi/impl/stubs/PyImportStatementElementType.class - com/jetbrains/python/psi/impl/stubs/PyNamedParameterElementType.class - com/jetbrains/python/psi/impl/stubs/PyParameterListElementType.class - com/jetbrains/python/psi/impl/stubs/PySingleStarParameterElementType.class - com/jetbrains/python/psi/impl/stubs/PyStarImportElementElementType.class - com/jetbrains/python/psi/impl/stubs/PyTargetExpressionElementType.class - com/jetbrains/python/psi/impl/stubs/PyTupleParameterElementType.class - com/jetbrains/python/psi/resolve/VariantsProcessor.class - com/jetbrains/python/psi/stubs/PyAnnotationOwnerStub.class - com/jetbrains/python/psi/stubs/PyAnnotationStub.class - com/jetbrains/python/psi/stubs/PyClassStub.class - com/jetbrains/python/psi/stubs/PyDecoratorListStub.class - com/jetbrains/python/psi/stubs/PyDecoratorStub.class - com/jetbrains/python/psi/stubs/PyExceptPartStub.class - com/jetbrains/python/psi/stubs/PyFileStub.class - com/jetbrains/python/psi/stubs/PyFromImportStatementStub.class - com/jetbrains/python/psi/stubs/PyFunctionStub.class - com/jetbrains/python/psi/stubs/PyImportElementStub.class - com/jetbrains/python/psi/stubs/PyImportStatementStub.class - com/jetbrains/python/psi/stubs/PyNamedParameterStub.class - com/jetbrains/python/psi/stubs/PyParameterListStub.class - com/jetbrains/python/psi/stubs/PySingleStarParameterStub.class - com/jetbrains/python/psi/stubs/PyStarImportElementStub.class - com/jetbrains/python/psi/stubs/PyTargetExpressionStub.class - com/jetbrains/python/psi/stubs/PyTupleParameterStub.class - com/jetbrains/python/psi/stubs/PyTypeCommentOwnerStub.class - com/jetbrains/python/psi/types/PyCallableType.class - com/jetbrains/python/psi/types/PyClassLikeType.class - com/jetbrains/python/psi/types/PyInstantiableType.class - com/jetbrains/python/psi/types/PyType.class - com/jetbrains/python/refactoring/PyDefUseUtil$InstructionNotFoundException.class - com/jetbrains/python/remote/PythonRemoteInterpreterManager$PyRemoteInterpreterExecutionException.class - com/jetbrains/python/run/PythonProcessHandler.class - com/jetbrains/python/run/PythonRunParams.class - com/jetbrains/python/toolbox/Maybe.class - - @@ -941,8 +135,8 @@ - 8000000 - 7000000 + 2800000 + 2600000 ${project.build.directory}/${project.build.finalName}.jar diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonHighlighter.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonHighlighter.java index f78a4f6862..4d7eab3556 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonHighlighter.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonHighlighter.java @@ -19,25 +19,33 @@ */ package org.sonar.plugins.python; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiWhiteSpace; -import com.intellij.psi.impl.source.tree.LeafPsiElement; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.PyElementType; -import com.jetbrains.python.psi.PyRecursiveElementVisitor; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.highlighting.NewHighlighting; import org.sonar.api.batch.sensor.highlighting.TypeOfText; -import org.sonar.python.frontend.PythonKeyword; -import org.sonar.python.frontend.PythonTokenLocation; +import org.sonar.python.PythonCheck; +import org.sonar.python.PythonVisitor; +import org.sonar.python.TokenLocation; +import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.PythonKeyword; +import org.sonar.python.api.PythonTokenType; + +import static com.sonar.sslr.api.GenericTokenType.IDENTIFIER; /** * Colors Python code. Currently colors: *
    - *
  • - * String literals. Examples: - *
    + *   
  • + * String literals. Examples: + *
      *       "hello"
      *
      *       'hello'
    @@ -46,71 +54,110 @@
      *           hello again
      *       """
      *     
    - *
  • - *
  • - * Keywords. Example: - *
    + *   
  • + *
  • + * Keywords. Example: + *
      *       def
      *     
    - *
  • - *
  • - * Numbers. Example: - *
    + *   
  • + *
  • + * Numbers. Example: + *
      *        123
      *        123L
      *        123.45
      *        123.45e-10
      *        123+88.99J
      *     
    - * For a negative number, the "minus" sign is not colored. - *
  • - *
  • - * Comments. Example: - *
    + *     For a negative number, the "minus" sign is not colored.
    + *   
  • + *
  • + * Comments. Example: + *
      *        # some comment
      *     
    - *
  • + *
  • *
* Docstrings are handled (i.e., colored) as structured comments, not as normal string literals. * "Attribute docstrings" and "additional docstrings" (see PEP 258) are handled as normal string literals. * Reminder: a docstring is a string literal that occurs as the first statement in a module, * function, class, or method definition. */ -public class PythonHighlighter extends PyRecursiveElementVisitor { +public class PythonHighlighter extends PythonVisitor { private NewHighlighting newHighlighting; - PythonHighlighter(SensorContext context, InputFile inputFile) { + private Set docStringTokens; + + public PythonHighlighter(SensorContext context, InputFile inputFile) { + docStringTokens = new HashSet<>(); newHighlighting = context.newHighlighting(); newHighlighting.onFile(inputFile); } - NewHighlighting getNewHighlighting() { - return newHighlighting; + @Override + public Set subscribedKinds() { + return PythonCheck.immutableSet( + PythonGrammar.FUNCDEF, + PythonGrammar.CLASSDEF, + PythonGrammar.FILE_INPUT); } @Override - public void visitElement(PsiElement element) { - if (element instanceof LeafPsiElement && !(element instanceof PsiWhiteSpace)) { - LeafPsiElement leaf = (LeafPsiElement) element; - PyElementType elementType = (PyElementType) leaf.getElementType(); - if (PythonKeyword.isKeyword(elementType)) { - highlight(leaf, TypeOfText.KEYWORD); - } else if (PyTokenTypes.NUMERIC_LITERALS.contains(elementType)) { - highlight(leaf, TypeOfText.CONSTANT); - } else if (elementType == PyTokenTypes.DOCSTRING) { - highlight(leaf, TypeOfText.STRUCTURED_COMMENT); - } else if (PyTokenTypes.STRING_NODES.contains(elementType)) { - highlight(leaf, TypeOfText.STRING); - } else if (elementType == PyTokenTypes.END_OF_LINE_COMMENT) { - highlight(leaf, TypeOfText.COMMENT); + public void visitNode(AstNode astNode) { + if (astNode.is(PythonGrammar.FILE_INPUT)) { + checkFirstStatement(astNode.getFirstChild(PythonGrammar.STATEMENT)); + } else { + checkFirstStatement(astNode.getFirstChild(PythonGrammar.SUITE).getFirstChild(PythonGrammar.STATEMENT)); + } + } + + private void checkFirstStatement(@Nullable AstNode firstStatement) { + if (firstStatement != null) { + List tokens = firstStatement.getTokens(); + + if (tokens.size() == 2 && tokens.get(0).getType().equals(PythonTokenType.STRING)) { + // second token is NEWLINE + highlight(tokens.get(0), TypeOfText.STRUCTURED_COMMENT); + docStringTokens.add(tokens.get(0)); } } - super.visitElement(element); } - private void highlight(PsiElement token, TypeOfText typeOfText) { - PythonTokenLocation tokenLocation = new PythonTokenLocation(token); + @Override + public void visitToken(Token token) { + if (token.getType().equals(PythonTokenType.NUMBER)) { + highlight(token, TypeOfText.CONSTANT); + + } else if (token.getType() instanceof PythonKeyword) { + highlight(token, TypeOfText.KEYWORD); + + } else if (token.getType().equals(PythonTokenType.STRING) && !docStringTokens.contains(token)) { + highlight(token, TypeOfText.STRING); + + } else if (token.getType().equals(IDENTIFIER) && isPython3Keyword(token.getValue())) { + // async and await are keywords starting python 3.5, however, for compatibility with previous versions, we cannot consider them as real keywords + highlight(token, TypeOfText.KEYWORD); + + } + + for (Trivia trivia : token.getTrivia()) { + highlight(trivia.getToken(), TypeOfText.COMMENT); + } + } + + private static boolean isPython3Keyword(String value) { + return "await".equals(value) || "async".equals(value); + } + + @Override + public void leaveFile(@Nullable AstNode astNode) { + newHighlighting.save(); + } + + private void highlight(Token token, TypeOfText typeOfText) { + TokenLocation tokenLocation = new TokenLocation(token); newHighlighting.highlight(tokenLocation.startLine(), tokenLocation.startLineOffset(), tokenLocation.endLine(), tokenLocation.endLineOffset(), typeOfText); } diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java index 068732dbe7..15cafbec16 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java @@ -19,7 +19,6 @@ */ package org.sonar.plugins.python; -import com.jetbrains.python.psi.PyFile; import com.sonar.sslr.api.Grammar; import com.sonar.sslr.api.RecognitionException; import com.sonar.sslr.impl.Parser; @@ -46,9 +45,8 @@ import org.sonar.python.PythonConfiguration; import org.sonar.python.PythonFile; import org.sonar.python.PythonVisitorContext; -import org.sonar.python.SubscriptionVisitor; +import org.sonar.python.metrics.FileLinesVisitor; import org.sonar.python.metrics.FileMetrics; -import org.sonar.python.metrics.MetricsVisitor; import org.sonar.python.parser.PythonParser; public class PythonScanner { @@ -90,12 +88,9 @@ public void scanFiles() { private void scanFile(InputFile inputFile) { PythonFile pythonFile = SonarQubePythonFile.create(inputFile); PythonVisitorContext visitorContext; - String fileContent = pythonFile.content(); - PyFile pyFile = null; try { - visitorContext = new PythonVisitorContext(parser.parse(fileContent), pythonFile); - pyFile = new org.sonar.python.frontend.PythonParser().parse(fileContent); - saveMeasures(inputFile, pyFile, fileContent); + visitorContext = new PythonVisitorContext(parser.parse(pythonFile.content()), pythonFile); + saveMeasures(inputFile, visitorContext); } catch (RecognitionException e) { visitorContext = new PythonVisitorContext(pythonFile, e); LOG.error("Unable to parse file: " + inputFile.toString()); @@ -110,15 +105,9 @@ private void scanFile(InputFile inputFile) { for (PythonCheck check : checks.all()) { check.scanFile(visitorContext); } - - if (pyFile != null) { - SubscriptionVisitor.analyze(checks.all(), visitorContext, pyFile); - PythonHighlighter pythonHighlighter = new PythonHighlighter(context, inputFile); - pyFile.accept(pythonHighlighter); - pythonHighlighter.getNewHighlighting().save(); - } - saveIssues(inputFile, visitorContext.getIssues()); + + new PythonHighlighter(context, inputFile).scanFile(visitorContext); } private void saveIssues(InputFile inputFile, List issues) { @@ -162,28 +151,28 @@ private static NewIssueLocation newLocation(InputFile inputFile, NewIssue issue, return newLocation; } - private void saveMeasures(InputFile inputFile, PyFile pyFile, String fileContent) { + private void saveMeasures(InputFile inputFile, PythonVisitorContext visitorContext) { boolean ignoreHeaderComments = new PythonConfiguration(context.fileSystem().encoding()).getIgnoreHeaderComments(); - FileMetrics fileMetrics = new FileMetrics(ignoreHeaderComments, pyFile); - MetricsVisitor metricsVisitor = fileMetrics.metricsVisitor(); + FileMetrics fileMetrics = new FileMetrics(visitorContext, ignoreHeaderComments); + FileLinesVisitor fileLinesVisitor = fileMetrics.fileLinesVisitor(); - cpdAnalyzer.pushCpdTokens(inputFile, pyFile, fileContent); - noSonarFilter.noSonarInFile(inputFile, metricsVisitor.getLinesWithNoSonar()); + cpdAnalyzer.pushCpdTokens(inputFile, visitorContext); + noSonarFilter.noSonarInFile(inputFile, fileLinesVisitor.getLinesWithNoSonar()); - Set linesOfCode = metricsVisitor.getLinesOfCode(); + Set linesOfCode = fileLinesVisitor.getLinesOfCode(); saveMetricOnFile(inputFile, CoreMetrics.NCLOC, linesOfCode.size()); saveMetricOnFile(inputFile, CoreMetrics.STATEMENTS, fileMetrics.numberOfStatements()); saveMetricOnFile(inputFile, CoreMetrics.FUNCTIONS, fileMetrics.numberOfFunctions()); saveMetricOnFile(inputFile, CoreMetrics.CLASSES, fileMetrics.numberOfClasses()); saveMetricOnFile(inputFile, CoreMetrics.COMPLEXITY, fileMetrics.complexity()); saveMetricOnFile(inputFile, CoreMetrics.COGNITIVE_COMPLEXITY, fileMetrics.cognitiveComplexity()); - saveMetricOnFile(inputFile, CoreMetrics.COMMENT_LINES, metricsVisitor.getCommentLineCount()); + saveMetricOnFile(inputFile, CoreMetrics.COMMENT_LINES, fileLinesVisitor.getCommentLineCount()); FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile); for (int line : linesOfCode) { fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1); } - for (int line : metricsVisitor.getExecutableLines()) { + for (int line : fileLinesVisitor.getExecutableLines()) { fileLinesContext.setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, line, 1); } fileLinesContext.save(); diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/cpd/PythonCpdAnalyzer.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/cpd/PythonCpdAnalyzer.java index 8fd804a82d..9741b84bb5 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/cpd/PythonCpdAnalyzer.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/cpd/PythonCpdAnalyzer.java @@ -19,79 +19,56 @@ */ package org.sonar.plugins.python.cpd; -import com.intellij.openapi.editor.Document; -import com.intellij.psi.PsiElement; -import com.intellij.psi.tree.IElementType; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.lexer.PythonIndentingLexer; -import com.jetbrains.python.psi.PyElementType; -import com.jetbrains.python.psi.PyFile; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import javax.annotation.CheckForNull; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.GenericTokenType; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.TokenType; +import java.util.List; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.cpd.NewCpdTokens; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.python.frontend.PythonParser; -import org.sonar.python.frontend.PythonTokenLocation; +import org.sonar.python.PythonVisitorContext; +import org.sonar.python.TokenLocation; +import org.sonar.python.api.PythonTokenType; public class PythonCpdAnalyzer { private final SensorContext context; - private static final Set IGNORED_TOKEN_TYPES = new HashSet<>(Arrays.asList( - PyTokenTypes.LINE_BREAK, PyTokenTypes.DEDENT, PyTokenTypes.INDENT, PyTokenTypes.END_OF_LINE_COMMENT, PyTokenTypes.SPACE, PyTokenTypes.STATEMENT_BREAK)); - private static final Logger LOG = Loggers.get(PythonCpdAnalyzer.class); public PythonCpdAnalyzer(SensorContext context) { this.context = context; } - public void pushCpdTokens(InputFile inputFile, PyFile pyFile, String fileContent) { - Document document = getDocument(pyFile); - if (document == null) { - LOG.debug("Cannot complete CPD analysis: PSIDocument is null."); - return; - } - PythonIndentingLexer lexer = new PythonIndentingLexer(); - lexer.start(PythonParser.normalizeEol(fileContent)); - NewCpdTokens cpdTokens = context.newCpdTokens().onFile(inputFile); - IElementType prevTokenType = null; - while (lexer.getTokenType() != null) { - IElementType currentTokenType = lexer.getTokenType(); - // INDENT/DEDENT could not be completely ignored during CPD see https://docs.python.org/3/reference/lexical_analysis.html#indentation - // Just taking into account DEDENT is enough, but because the DEDENT token has an empty value, it's the - // following new line which is added in its place to create a difference - if (isNewLineWithIndentationChange(prevTokenType, currentTokenType) || !IGNORED_TOKEN_TYPES.contains(currentTokenType)) { - int tokenEnd = lexer.getTokenEnd(); - String tokenText = lexer.getTokenText(); - if (currentTokenType == PyTokenTypes.LINE_BREAK) { - tokenText = "\n"; - tokenEnd = lexer.getTokenStart() + 1; + public void pushCpdTokens(InputFile inputFile, PythonVisitorContext visitorContext) { + AstNode root = visitorContext.rootTree(); + if (root != null) { + NewCpdTokens cpdTokens = context.newCpdTokens().onFile(inputFile); + List tokens = root.getTokens(); + for (int i = 0; i < tokens.size(); i++) { + Token token = tokens.get(i); + TokenType currentTokenType = token.getType(); + TokenType nextTokenType = i + 1 < tokens.size() ? tokens.get(i + 1).getType() : GenericTokenType.EOF; + // INDENT/DEDENT could not be completely ignored during CPD see https://docs.python.org/3/reference/lexical_analysis.html#indentation + // Just taking into account DEDENT is enough, but because the DEDENT token has an empty value, it's the + // preceding new line which is added in its place to create a difference + if (isNewLineWithIndentationChange(currentTokenType, nextTokenType) || !isIgnoredType(currentTokenType)) { + TokenLocation location = new TokenLocation(token); + cpdTokens.addToken(location.startLine(), location.startLineOffset(), location.endLine(), location.endLineOffset(), token.getValue()); } - PythonTokenLocation location = new PythonTokenLocation(lexer.getTokenStart(), tokenEnd, document); - cpdTokens.addToken(location.startLine(), location.startLineOffset(), location.endLine(), location.endLineOffset(), tokenText); } - prevTokenType = currentTokenType; - lexer.advance(); + cpdTokens.save(); } - - cpdTokens.save(); } - private static boolean isNewLineWithIndentationChange(@CheckForNull IElementType prevTokenType, IElementType currentTokenType) { - return prevTokenType != null && prevTokenType == PyTokenTypes.DEDENT && currentTokenType == PyTokenTypes.LINE_BREAK; + private static boolean isNewLineWithIndentationChange(TokenType currentTokenType, TokenType nextTokenType) { + return currentTokenType.equals(PythonTokenType.NEWLINE) && nextTokenType.equals(PythonTokenType.DEDENT); } - @CheckForNull - private static Document getDocument(PyFile pyFile) { - PsiElement root = pyFile.getFirstChild(); - if (root == null) { - return null; - } - return root.getContainingFile().getViewProvider().getDocument(); + private static boolean isIgnoredType(TokenType type) { + return type.equals(PythonTokenType.NEWLINE) || + type.equals(PythonTokenType.DEDENT) || + type.equals(PythonTokenType.INDENT) || + type.equals(GenericTokenType.EOF); } } diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonHighlighterTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonHighlighterTest.java index 242e3d934f..200edd6b22 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonHighlighterTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonHighlighterTest.java @@ -29,7 +29,7 @@ import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.sensor.highlighting.TypeOfText; import org.sonar.api.batch.sensor.internal.SensorContextTester; -import org.sonar.python.frontend.PythonParser; +import org.sonar.python.TestPythonVisitorRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -51,10 +51,8 @@ public void scanFile() { context = SensorContextTester.create(new File(dir)); context.fileSystem().add(inputFile); - String content = TestUtils.fileContent(file, StandardCharsets.UTF_8); PythonHighlighter pythonHighlighter = new PythonHighlighter(context, inputFile); - new PythonParser().parse(content).accept(pythonHighlighter); - pythonHighlighter.getNewHighlighting().save(); + TestPythonVisitorRunner.scanFile(file, pythonHighlighter); } @Test @@ -77,16 +75,11 @@ public void keyword() { // pass checkOnRange(9, 4, 4, TypeOfText.KEYWORD); - /* - * async and await are keywords starting python 3.5 - * For the time being we parsing using PyCharm v2.7 - so they are not considered as keywords - */ - // async - // checkOnRange(95, 0, 5, TypeOfText.KEYWORD); + checkOnRange(95, 0, 5, TypeOfText.KEYWORD); // await - // checkOnRange(98, 0, 5, TypeOfText.KEYWORD); + checkOnRange(98, 0, 5, TypeOfText.KEYWORD); } @Test diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSquidSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSquidSensorTest.java index 81a05280ca..cced16d172 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSquidSensorTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSquidSensorTest.java @@ -111,7 +111,7 @@ public void test_execute_on_sonarlint() { String key = "moduleKey:file1.py"; assertThat(context.measure(key, CoreMetrics.NCLOC).value()).isEqualTo(22); - assertThat(context.measure(key, CoreMetrics.STATEMENTS).value()).isEqualTo(22); + assertThat(context.measure(key, CoreMetrics.STATEMENTS).value()).isEqualTo(20); assertThat(context.measure(key, CoreMetrics.FUNCTIONS).value()).isEqualTo(4); assertThat(context.measure(key, CoreMetrics.CLASSES).value()).isEqualTo(1); assertThat(context.measure(key, CoreMetrics.COMPLEXITY).value()).isEqualTo(5); diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/cpd/PythonCpdAnalyzerTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/cpd/PythonCpdAnalyzerTest.java index 4ec0df0492..2d7bcf8a6f 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/cpd/PythonCpdAnalyzerTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/cpd/PythonCpdAnalyzerTest.java @@ -19,8 +19,8 @@ */ package org.sonar.plugins.python.cpd; -import com.jetbrains.python.psi.PyFile; import java.io.File; +import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; @@ -32,7 +32,8 @@ import org.sonar.api.batch.sensor.internal.SensorContextTester; import org.sonar.plugins.python.Python; import org.sonar.plugins.python.TestUtils; -import org.sonar.python.frontend.PythonParser; +import org.sonar.python.PythonVisitorContext; +import org.sonar.python.TestPythonVisitorRunner; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; @@ -45,21 +46,9 @@ public class PythonCpdAnalyzerTest { @Test public void code_chunks_2() { - File file = new File(BASE_DIR, "code_chunks_2.py"); - - String content = TestUtils.fileContent(file, UTF_8); - DefaultInputFile inputFile = TestInputFileBuilder.create("moduleKey", file.getName()) - .setModuleBaseDir(Paths.get(BASE_DIR)) - .setCharset(UTF_8) - .setType(InputFile.Type.MAIN) - .setLanguage(Python.KEY) - .initMetadata(content) - .build(); - - context.fileSystem().add(inputFile); - - PyFile pyFile = new PythonParser().parse(content); - cpdAnalyzer.pushCpdTokens(inputFile, pyFile, content); + DefaultInputFile inputFile = inputFile("code_chunks_2.py"); + PythonVisitorContext visitorContext = TestPythonVisitorRunner.createContext(inputFile.path().toFile()); + cpdAnalyzer.pushCpdTokens(inputFile, visitorContext); List lines = context.cpdTokens("moduleKey:code_chunks_2.py"); assertThat(lines).isNotNull().hasSize(25); @@ -100,4 +89,19 @@ public void code_chunks_2() { "[itemforiteminitems]"); } + private DefaultInputFile inputFile(String fileName) { + File file = new File(BASE_DIR, fileName); + + DefaultInputFile inputFile = TestInputFileBuilder.create("moduleKey", file.getName()) + .setModuleBaseDir(Paths.get(BASE_DIR)) + .setCharset(UTF_8) + .setType(InputFile.Type.MAIN) + .setLanguage(Python.KEY) + .initMetadata(TestUtils.fileContent(file, StandardCharsets.UTF_8)) + .build(); + + context.fileSystem().add(inputFile); + + return inputFile; + } } diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/minimization/ClassLoaderLogAnalyzer.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/minimization/ClassLoaderLogAnalyzer.java deleted file mode 100644 index 0c54e8d6cf..0000000000 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/minimization/ClassLoaderLogAnalyzer.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.plugins.python.minimization; - -import com.google.common.io.Files; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class ClassLoaderLogAnalyzer { - - private static final List JARS_TO_FILTER = new ArrayList<>(Arrays.asList( - new Jar("com.jetbrains.pycharm", "extensions"), - new Jar("org.jetbrains.intellij.deps", "trove4j"), - new Jar("org.jetbrains.kotlin", "kotlin-stdlib"), - new Jar("com.jetbrains.pycharm", "platform-impl"), - new Jar("com.jetbrains.pycharm", "pycharm-pydev"), - new Jar("com.jetbrains.pycharm", "util").resource("misc/registry.properties"), - new Jar("com.jetbrains.pycharm", "resources_en").resource("com/jetbrains/python/PyBundle.properties"), - new Jar("com.jetbrains.pycharm", "openapi"), - new Jar("com.jetbrains.pycharm", "platform-api"), - new Jar("com.jetbrains.pycharm", "pycharm"))); - - public static void main(String[] args) throws IOException { - File file = new File("sonar-python-plugin/target/class-logs.txt"); - String fileContent = Files.toString(file, StandardCharsets.UTF_8); - List lines = Arrays.stream(fileContent.split("\\n")) - .map(Line::parse) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - Map> classesBySource = new TreeMap<>(lines.stream() - .collect(Collectors.groupingBy(Line::source))); - - for (Jar jar : JARS_TO_FILTER) { - List classes = classesBySource.entrySet().stream() - .filter(e -> e.getKey().contains("/" + jar.groupId.replace('.', '/') + "/" + jar.artifactId + "/")) - .map(Map.Entry::getValue) - .findFirst() - .orElse(Collections.emptyList()); - - System.out.println( - "\n" + - " " + jar.groupId + ":" + jar.artifactId + "\n" + - " "); - - Map> classesByPackage = new TreeMap<>(classes.stream().collect(Collectors.groupingBy(Line::packageName))); - for (Map.Entry> entry : classesByPackage.entrySet()) { - List packageLines = entry.getValue(); - if (packageLines.size() > 50) { - System.out.println(" " + entry.getKey().replaceAll("\\.", "/") + "/*.class"); - } else { - packageLines.stream() - .map(Line::classFileName) - .sorted() - .map(l -> " " + l + "") - .forEach(System.out::println); - } - } - - jar.resources.stream() - .map(l -> " " + l + "") - .forEach(System.out::println); - - System.out.println(" "); - System.out.println(""); - } - - } - - private static class Jar { - - private final String groupId; - private final String artifactId; - private final List resources = new ArrayList<>(); - - private Jar(String groupId, String artifactId) { - this.groupId = groupId; - this.artifactId = artifactId; - } - - Jar resource(String resource) { - resources.add(resource); - return this; - } - } - - private static class Line { - - private static final Pattern PATTERN_JDK8 = Pattern.compile("\\[Loaded (.*) from (.*/.*)]"); - private static final Pattern PATTERN_JDK11 = Pattern.compile("\\[.*]\\[info]\\[class,load] (.*) source: (.*/.*)"); - - String classFileName() { - return fullClassName.replaceAll("\\.", "/") + ".class"; - } - - String fullClassName; - String source; - - Line(String fullClassName, String source) { - this.fullClassName = fullClassName; - this.source = source; - } - - static Line parse(String line) { - Pattern pattern = line.startsWith("[Loaded ") ? PATTERN_JDK8 : PATTERN_JDK11; - Matcher matcher = pattern.matcher(line); - if (!matcher.matches()) { - return null; - } - return new Line(matcher.group(1), matcher.group(2)); - } - - String source() { - return source; - } - - String packageName() { - return fullClassName.substring(0, fullClassName.lastIndexOf('.')); - } - } - -} diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/minimization/ParserCaller.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/minimization/ParserCaller.java deleted file mode 100644 index 2ac5927bb4..0000000000 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/minimization/ParserCaller.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.plugins.python.minimization; - -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFormattedStringElement; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import org.mockito.Mockito; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.fs.internal.DefaultInputFile; -import org.sonar.api.batch.fs.internal.TestInputFileBuilder; -import org.sonar.api.batch.rule.Checks; -import org.sonar.api.batch.sensor.internal.SensorContextTester; -import org.sonar.api.issue.NoSonarFilter; -import org.sonar.api.measures.FileLinesContext; -import org.sonar.api.measures.FileLinesContextFactory; -import org.sonar.api.rule.RuleKey; -import org.sonar.plugins.python.PythonScanner; -import org.sonar.plugins.python.TestUtils; -import org.sonar.python.PythonCheck; -import org.sonar.python.checks.CheckList; -import org.sonar.python.checks.CommentRegularExpressionCheck; -import org.sonar.python.frontend.PythonParser; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * To be executed in the IDE with the following JVM option: -verbose:class - */ -public class ParserCaller { - - private final PythonParser parser = new PythonParser(); - - public static void main(String[] args) { - ParserCaller test = new ParserCaller(); - test.python3_f_string(); - test.real_files("its/sources"); - test.real_files("python-checks/src/test/resources/checks"); - } - - private void python3_f_string() { - PyFile pyFile = parser.parse("f\"Hello {name}!\""); - PyFormattedStringElement stringElement = PsiTreeUtil.getParentOfType(pyFile.findElementAt(0), PyFormattedStringElement.class); - check(stringElement.getDecodedFragments().stream().map(pair -> pair.second).collect(Collectors.toList()).equals(Arrays.asList("Hello ", "{name}", "!"))); - } - - private void real_files(String directory) { - try { - Files.walk(Paths.get(directory)) - .filter(Files::isRegularFile) - .map(Path::toString) - .filter(s -> s.endsWith(".py")) - .forEach(this::parseFile); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void parseFile(String path) { - File file = new File(path); - DefaultInputFile inputFile = TestInputFileBuilder.create(".", file.getPath()) - .setCharset(StandardCharsets.UTF_8) - .initMetadata(TestUtils.fileContent(file, StandardCharsets.UTF_8)) - .build(); - SensorContextTester context = SensorContextTester.create(new File(".")); - context.fileSystem().add(inputFile); - - List checksAll = new ArrayList<>(); - CheckList.getChecks().forEach(aClass -> { - try { - PythonCheck instance = (PythonCheck) aClass.getConstructors()[0].newInstance(); - if (aClass == CommentRegularExpressionCheck.class) { - ((CommentRegularExpressionCheck) instance).regularExpression = "(?i).*TODO.*"; - } - checksAll.add(instance); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - Checks checks = Mockito.mock(Checks.class); - when(checks.all()).thenReturn(checksAll); - when(checks.ruleKey(Mockito.any())).thenReturn(mock(RuleKey.class)); - assertThat(checks.all().size()).isGreaterThan(10); - - FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); - FileLinesContext fileLinesContext = mock(FileLinesContext.class); - when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext); - PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, new NoSonarFilter(), Collections.singletonList(inputFile)); - scanner.scanFiles(); - } - - private void check(boolean expr) { - if (!expr) { - throw new IllegalStateException("Fail!"); - } - } -} diff --git a/sslr-python-toolkit/pom.xml b/sslr-python-toolkit/pom.xml index 9ebbf08adf..ee2030264c 100644 --- a/sslr-python-toolkit/pom.xml +++ b/sslr-python-toolkit/pom.xml @@ -74,6 +74,7 @@ ch.qos.logback:logback-core commons-io:commons-io commons-lang:commons-lang + com.google.guava:guava @@ -97,8 +98,8 @@ - 2000000 - 1000000 + 4200000 + 4000000 ${project.build.directory}/${project.build.finalName}.jar diff --git a/tools/download-pycharm.sh b/tools/download-pycharm.sh deleted file mode 100755 index 78c874dde5..0000000000 --- a/tools/download-pycharm.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -set -eu - -PYCHARM_VERSION=2019.1.3 - -curl -L -O https://download-cf.jetbrains.com/python/pycharm-community-${PYCHARM_VERSION}.tar.gz -tar xzf pycharm-community-${PYCHARM_VERSION}.tar.gz -rm pycharm-community-${PYCHARM_VERSION}.tar.gz - -cd pycharm-community-${PYCHARM_VERSION}/lib -mvn install:install-file -Dfile=extensions.jar -DgroupId=com.jetbrains.pycharm -DartifactId=extensions -Dversion=${PYCHARM_VERSION} -Dpackaging=jar -mvn install:install-file -Dfile=openapi.jar -DgroupId=com.jetbrains.pycharm -DartifactId=openapi -Dversion=${PYCHARM_VERSION} -Dpackaging=jar -mvn install:install-file -Dfile=platform-api.jar -DgroupId=com.jetbrains.pycharm -DartifactId=platform-api -Dversion=${PYCHARM_VERSION} -Dpackaging=jar -mvn install:install-file -Dfile=platform-impl.jar -DgroupId=com.jetbrains.pycharm -DartifactId=platform-impl -Dversion=${PYCHARM_VERSION} -Dpackaging=jar -mvn install:install-file -Dfile=pycharm.jar -DgroupId=com.jetbrains.pycharm -DartifactId=pycharm -Dversion=${PYCHARM_VERSION} -Dpackaging=jar -mvn install:install-file -Dfile=pycharm-pydev.jar -DgroupId=com.jetbrains.pycharm -DartifactId=pycharm-pydev -Dversion=${PYCHARM_VERSION} -Dpackaging=jar -mvn install:install-file -Dfile=resources_en.jar -DgroupId=com.jetbrains.pycharm -DartifactId=resources_en -Dversion=${PYCHARM_VERSION} -Dpackaging=jar -mvn install:install-file -Dfile=util.jar -DgroupId=com.jetbrains.pycharm -DartifactId=util -Dversion=${PYCHARM_VERSION} -Dpackaging=jar diff --git a/travis.sh b/travis.sh index 4fcf295704..db87900682 100755 --- a/travis.sh +++ b/travis.sh @@ -2,8 +2,6 @@ set -euo pipefail -./tools/download-pycharm.sh - function installTravisTools { mkdir -p ~/.local curl -sSL https://github.com/SonarSource/travis-utils/tarball/v56 | tar zx --strip-components 1 -C ~/.local @@ -16,5 +14,4 @@ export DEPLOY_PULL_REQUEST=true regular_mvn_build_deploy_analyze -# FIXME(mpaladin) re-enable license check -# ./check-license-compliance.sh +./check-license-compliance.sh