From 0cc31843c8e6a953b46f7964923d559ee8d52502 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Mon, 19 Aug 2019 16:08:07 +0200 Subject: [PATCH 01/35] Strongly typed AST: PyFileInputTree --- .../python/api/tree/PyFileInputTree.java | 26 ++++++++++++ .../python/api/tree/PyFileInputTreeImpl.java | 40 ++++++++++++++++++ .../python/api/tree/PyStatementTree.java | 23 +++++++++++ .../org/sonar/python/api/tree/PyTree.java | 41 +++++++++++++++++++ .../python/api/tree/PythonTreeMaker.java | 33 +++++++++++++++ .../java/org/sonar/python/api/tree/Tree.java | 33 +++++++++++++++ .../python/api/tree/PythonTreeMakerTest.java | 37 +++++++++++++++++ 7 files changed, 233 insertions(+) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/Tree.java create mode 100644 python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTree.java new file mode 100644 index 0000000000..639be1b810 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTree.java @@ -0,0 +1,26 @@ +/* + * 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.api.tree; + +import java.util.List; + +public interface PyFileInputTree extends Tree { + List statements(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java new file mode 100644 index 0000000000..26e4863a35 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java @@ -0,0 +1,40 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.AstNode; +import java.util.List; + +public class PyFileInputTreeImpl extends PyTree implements PyFileInputTree { + + public PyFileInputTreeImpl(AstNode astNode) { + super(astNode); + } + + @Override + public Tree.Kind getKind() { + return Tree.Kind.FILE_INPUT; + } + + @Override + public List statements() { + return null; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyStatementTree.java new file mode 100644 index 0000000000..5bef60bf0a --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyStatementTree.java @@ -0,0 +1,23 @@ +/* + * 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.api.tree; + +public interface PyStatementTree { +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java new file mode 100644 index 0000000000..c6e06fd3fe --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java @@ -0,0 +1,41 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import com.sonar.sslr.api.Token; +import javax.annotation.Nullable; + +public abstract class PyTree extends AstNode implements Tree { + public PyTree(AstNodeType type, String name, @Nullable Token token) { + super(type, name, token); + } + + public PyTree(Token token) { + super(token); + } + + public PyTree(AstNode node) { + super(node.getType(), node.getName(), node.getToken()); + } + + public abstract Kind getKind(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java new file mode 100644 index 0000000000..024258bb9b --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java @@ -0,0 +1,33 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.AstNode; +import org.sonar.python.api.PythonGrammar; + +public class PythonTreeMaker { + + public static PyTree convert(AstNode astNode) { + if (astNode.is(PythonGrammar.FILE_INPUT)) { + return new PyFileInputTreeImpl(astNode); + } + return null; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java new file mode 100644 index 0000000000..910eaf08c2 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -0,0 +1,33 @@ +/* + * 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.api.tree; + +public interface Tree { + + enum Kind { + FILE_INPUT(PyFileInputTree.class); + + final Class associatedInterface; + + Kind(Class associatedInterface) { + this.associatedInterface = associatedInterface; + } + } +} diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java new file mode 100644 index 0000000000..c59aa0eee3 --- /dev/null +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -0,0 +1,37 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.AstNode; +import org.junit.Test; +import org.sonar.python.parser.RuleTest; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PythonTreeMakerTest extends RuleTest { + + @Test + public void fileInputTree() { + AstNode astNode = p.parse(""); + PyTree pyTree = PythonTreeMaker.convert(astNode); + assertThat(pyTree).isInstanceOf(PyFileInputTree.class); + } + +} From 0f96cd03d89eb202d3870487a06f7927f86f77a4 Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Mon, 19 Aug 2019 16:36:52 +0200 Subject: [PATCH 02/35] Use PyFileInput as root tree --- .../org/sonar/python/PythonVisitorContext.java | 10 ++++++---- .../org/sonar/python/TestPythonVisitorRunner.java | 5 +++-- .../python/api/tree/PyFileInputTreeImpl.java | 3 ++- .../java/org/sonar/python/api/tree/PyTree.java | 15 +++++---------- .../sonar/python/api/tree/PythonTreeMaker.java | 9 +++------ .../python/api/tree/PythonTreeMakerTest.java | 4 ++-- .../org/sonar/plugins/python/PythonScanner.java | 5 ++++- 7 files changed, 25 insertions(+), 26 deletions(-) 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 2d49a3b37e..d01311bc9b 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java +++ b/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java @@ -24,18 +24,20 @@ import java.util.ArrayList; import java.util.List; import org.sonar.python.PythonCheck.PreciseIssue; +import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyFileInputTreeImpl; import org.sonar.python.semantic.SymbolTable; import org.sonar.python.semantic.SymbolTableBuilderVisitor; public class PythonVisitorContext { - private final AstNode rootTree; + private final PyFileInputTreeImpl rootTree; private final PythonFile pythonFile; private final RecognitionException parsingException; private SymbolTable symbolTable = null; private List issues = new ArrayList<>(); - public PythonVisitorContext(AstNode rootTree, PythonFile pythonFile) { + public PythonVisitorContext(PyFileInputTree rootTree, PythonFile pythonFile) { this(rootTree, pythonFile, null); SymbolTableBuilderVisitor symbolTableBuilderVisitor = new SymbolTableBuilderVisitor(); symbolTableBuilderVisitor.scanFile(this); @@ -46,8 +48,8 @@ public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingE this(null, pythonFile, parsingException); } - private PythonVisitorContext(AstNode rootTree, PythonFile pythonFile, RecognitionException parsingException) { - this.rootTree = rootTree; + private PythonVisitorContext(PyFileInputTree rootTree, PythonFile pythonFile, RecognitionException parsingException) { + this.rootTree = (PyFileInputTreeImpl) rootTree; this.pythonFile = pythonFile; this.parsingException = parsingException; } diff --git a/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java b/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java index b593f58417..13a7a2ffc8 100644 --- a/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java +++ b/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java @@ -19,13 +19,14 @@ */ package org.sonar.python; -import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Grammar; import com.sonar.sslr.impl.Parser; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PythonTreeMaker; import org.sonar.python.parser.PythonParser; public class TestPythonVisitorRunner { @@ -43,7 +44,7 @@ public static void scanFile(File file, PythonVisitor... visitors) { public static PythonVisitorContext createContext(File file) { Parser parser = PythonParser.create(new PythonConfiguration(StandardCharsets.UTF_8)); TestPythonFile pythonFile = new TestPythonFile(file); - AstNode rootTree = parser.parse(pythonFile.content()); + PyFileInputTree rootTree = new PythonTreeMaker().fileInput(parser.parse(pythonFile.content())); return new PythonVisitorContext(rootTree, pythonFile); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java index 26e4863a35..dafc6af4e1 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java @@ -20,6 +20,7 @@ package org.sonar.python.api.tree; import com.sonar.sslr.api.AstNode; +import java.util.Collections; import java.util.List; public class PyFileInputTreeImpl extends PyTree implements PyFileInputTree { @@ -35,6 +36,6 @@ public Tree.Kind getKind() { @Override public List statements() { - return null; + return Collections.emptyList(); } } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java index c6e06fd3fe..ece281d3d4 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java @@ -20,21 +20,16 @@ package org.sonar.python.api.tree; import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; -import com.sonar.sslr.api.Token; -import javax.annotation.Nullable; public abstract class PyTree extends AstNode implements Tree { - public PyTree(AstNodeType type, String name, @Nullable Token token) { - super(type, name, token); - } - - public PyTree(Token token) { - super(token); - } + private final AstNode node; public PyTree(AstNode node) { super(node.getType(), node.getName(), node.getToken()); + this.node = node; + for (AstNode child : node.getChildren()) { + addChild(child); + } } public abstract Kind getKind(); diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java index 024258bb9b..3b196addcf 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java @@ -20,14 +20,11 @@ package org.sonar.python.api.tree; import com.sonar.sslr.api.AstNode; -import org.sonar.python.api.PythonGrammar; public class PythonTreeMaker { - public static PyTree convert(AstNode astNode) { - if (astNode.is(PythonGrammar.FILE_INPUT)) { - return new PyFileInputTreeImpl(astNode); - } - return null; + public PyFileInputTree fileInput(AstNode astNode) { + PyFileInputTreeImpl root = new PyFileInputTreeImpl(astNode); + return root; } } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index c59aa0eee3..2e9c33fcd5 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -30,8 +30,8 @@ public class PythonTreeMakerTest extends RuleTest { @Test public void fileInputTree() { AstNode astNode = p.parse(""); - PyTree pyTree = PythonTreeMaker.convert(astNode); - assertThat(pyTree).isInstanceOf(PyFileInputTree.class); + PyFileInputTree pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).isEmpty(); } } 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 15cafbec16..667aba34ec 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 @@ -45,6 +45,8 @@ import org.sonar.python.PythonConfiguration; import org.sonar.python.PythonFile; import org.sonar.python.PythonVisitorContext; +import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PythonTreeMaker; import org.sonar.python.metrics.FileLinesVisitor; import org.sonar.python.metrics.FileMetrics; import org.sonar.python.parser.PythonParser; @@ -89,7 +91,8 @@ private void scanFile(InputFile inputFile) { PythonFile pythonFile = SonarQubePythonFile.create(inputFile); PythonVisitorContext visitorContext; try { - visitorContext = new PythonVisitorContext(parser.parse(pythonFile.content()), pythonFile); + PyFileInputTree parse = new PythonTreeMaker().fileInput(parser.parse(pythonFile.content())); + visitorContext = new PythonVisitorContext(parse, pythonFile); saveMeasures(inputFile, visitorContext); } catch (RecognitionException e) { visitorContext = new PythonVisitorContext(pythonFile, e); From e6ec23156bae514d58c3d302307cae9f736d60f0 Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Mon, 19 Aug 2019 16:49:01 +0200 Subject: [PATCH 03/35] Refactor impl to dedicated package and bootstrap statement creation --- .../org/sonar/python/PythonVisitorContext.java | 2 +- .../sonar/python/api/tree/PyStatementTree.java | 2 +- .../sonar/python/api/tree/PythonTreeMaker.java | 16 ++++++++++++++-- .../{api => }/tree/PyFileInputTreeImpl.java | 16 +++++++++++----- .../python/api/tree/PythonTreeMakerTest.java | 2 +- 5 files changed, 28 insertions(+), 10 deletions(-) rename python-squid/src/main/java/org/sonar/python/{api => }/tree/PyFileInputTreeImpl.java (72%) 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 d01311bc9b..1dbcc74110 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java +++ b/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java @@ -25,7 +25,7 @@ import java.util.List; import org.sonar.python.PythonCheck.PreciseIssue; import org.sonar.python.api.tree.PyFileInputTree; -import org.sonar.python.api.tree.PyFileInputTreeImpl; +import org.sonar.python.tree.PyFileInputTreeImpl; import org.sonar.python.semantic.SymbolTable; import org.sonar.python.semantic.SymbolTableBuilderVisitor; diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyStatementTree.java index 5bef60bf0a..f2645d08da 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyStatementTree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyStatementTree.java @@ -19,5 +19,5 @@ */ package org.sonar.python.api.tree; -public interface PyStatementTree { +public interface PyStatementTree extends Tree { } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java index 3b196addcf..26bfd20f10 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java @@ -20,11 +20,23 @@ package org.sonar.python.api.tree; import com.sonar.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Collectors; +import org.sonar.python.api.PythonGrammar; +import org.sonar.python.tree.PyFileInputTreeImpl; public class PythonTreeMaker { public PyFileInputTree fileInput(AstNode astNode) { - PyFileInputTreeImpl root = new PyFileInputTreeImpl(astNode); - return root; + List statements = astNode.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); + return new PyFileInputTreeImpl(astNode, statements); } + + private PyStatementTree statement(AstNode astNode) { + if (astNode.is(PythonGrammar.IF_STMT)) { + } + return null; + } + + } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java similarity index 72% rename from python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java rename to python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java index dafc6af4e1..3374a1fee2 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyFileInputTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java @@ -17,25 +17,31 @@ * 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.api.tree; +package org.sonar.python.tree; import com.sonar.sslr.api.AstNode; -import java.util.Collections; import java.util.List; +import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTree; +import org.sonar.python.api.tree.Tree; public class PyFileInputTreeImpl extends PyTree implements PyFileInputTree { - public PyFileInputTreeImpl(AstNode astNode) { + private final List statements; + + public PyFileInputTreeImpl(AstNode astNode, List statements) { super(astNode); + this.statements = statements; } @Override - public Tree.Kind getKind() { + public Kind getKind() { return Tree.Kind.FILE_INPUT; } @Override public List statements() { - return Collections.emptyList(); + return statements; } } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 2e9c33fcd5..fe601a3aba 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -28,7 +28,7 @@ public class PythonTreeMakerTest extends RuleTest { @Test - public void fileInputTree() { + public void fileInputTreeOnEmptyFile() { AstNode astNode = p.parse(""); PyFileInputTree pyTree = new PythonTreeMaker().fileInput(astNode); assertThat(pyTree.statements()).isEmpty(); From b7de74caa269fa9af11f06a6f3be7cb5dd4347e1 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Mon, 19 Aug 2019 17:54:31 +0200 Subject: [PATCH 04/35] bootstrap PyIfStatementTree --- .../python/api/tree/PyElseStatementTree.java | 30 +++++++ .../python/api/tree/PyExpressionTree.java | 4 + .../python/api/tree/PyIfStatementTree.java | 43 ++++++++++ .../python/api/tree/PythonTreeMaker.java | 27 +++++++ .../python/tree/PyExpressionTreeImpl.java | 9 +++ .../python/tree/PyIfStatementTreeImpl.java | 80 +++++++++++++++++++ .../python/api/tree/PythonTreeMakerTest.java | 10 +++ 7 files changed, 203 insertions(+) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyElseStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyIfStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyElseStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyElseStatementTree.java new file mode 100644 index 0000000000..b5e6f60402 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyElseStatementTree.java @@ -0,0 +1,30 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyElseStatementTree extends PyStatementTree { + Token elseKeyword(); + + List body(); + +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionTree.java new file mode 100644 index 0000000000..f03bf037da --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionTree.java @@ -0,0 +1,4 @@ +package org.sonar.python.api.tree; + +public interface PyExpressionTree extends Tree { +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyIfStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyIfStatementTree.java new file mode 100644 index 0000000000..fcdab0d9f5 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyIfStatementTree.java @@ -0,0 +1,43 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +/** + * if-elif-else statement + */ +public interface PyIfStatementTree extends PyStatementTree { + Token keyword(); + + PyExpressionTree condition(); + + List body(); + + List elifBranches(); + + boolean isElif(); + + @CheckForNull + PyElseStatementTree elseBranch(); + +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java index 26bfd20f10..158487e37a 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java @@ -20,10 +20,14 @@ package org.sonar.python.api.tree; import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; import java.util.List; import java.util.stream.Collectors; import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.PythonKeyword; +import org.sonar.python.tree.PyExpressionTreeImpl; import org.sonar.python.tree.PyFileInputTreeImpl; +import org.sonar.python.tree.PyIfStatementTreeImpl; public class PythonTreeMaker { @@ -34,9 +38,32 @@ public PyFileInputTree fileInput(AstNode astNode) { private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.IF_STMT)) { + return ifStatement(astNode); } return null; } + public PyIfStatementTree ifStatement(AstNode astNode) { + Token ifToken = astNode.getTokens().get(0); + AstNode condition = astNode.getFirstChild(PythonGrammar.TEST); + AstNode suite = astNode.getFirstChild(PythonGrammar.SUITE); + List statements = suite.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); + AstNode elseSuite = astNode.getLastChild(PythonGrammar.SUITE); + boolean hasElse = false; + if (elseSuite.getPreviousSibling().getPreviousSibling().is(PythonKeyword.ELSE)) { + hasElse = true; + } + return new PyIfStatementTreeImpl( + astNode, ifToken, expression(condition), statements); + } + + private PyElseStatementTree elseStatement(AstNode astNode) { + return null; + } + + PyExpressionTree expression(AstNode astNode) { + return new PyExpressionTreeImpl(astNode); + } + } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java new file mode 100644 index 0000000000..557d1f8339 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java @@ -0,0 +1,9 @@ +package org.sonar.python.tree; + +import com.sonar.sslr.api.AstNode; +import org.sonar.python.api.tree.PyExpressionTree; + +public class PyExpressionTreeImpl implements PyExpressionTree { + public PyExpressionTreeImpl(AstNode astNode) { + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java new file mode 100644 index 0000000000..ed89c92964 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java @@ -0,0 +1,80 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTree; + +public class PyIfStatementTreeImpl extends PyTree implements PyIfStatementTree { + + private final Token keyword; + private final PyExpressionTree condition; + private final List statements; + + public PyIfStatementTreeImpl(AstNode node, Token keyword, PyExpressionTree condition, List statements) { + super(node); + this.keyword = keyword; + this.condition = condition; + this.statements = statements; + } + + @Override + public Token keyword() { + return keyword; + } + + @Override + public PyExpressionTree condition() { + return condition; + } + + @Override + public List body() { + return null; + } + + @Override + public List elifBranches() { + return null; + } + + @Override + public boolean isElif() { + return false; + } + + @CheckForNull + @Override + public PyElseStatementTree elseBranch() { + return null; + } + + @Override + public Kind getKind() { + return null; + } +} diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index fe601a3aba..fcbe3eaa68 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -21,6 +21,7 @@ import com.sonar.sslr.api.AstNode; import org.junit.Test; +import org.sonar.python.api.PythonGrammar; import org.sonar.python.parser.RuleTest; import static org.assertj.core.api.Assertions.assertThat; @@ -34,4 +35,13 @@ public void fileInputTreeOnEmptyFile() { assertThat(pyTree.statements()).isEmpty(); } + @Test + public void fileInputTreeWithIfStatement() { + setRootRule(PythonGrammar.IF_STMT); + AstNode astNode = p.parse("if x: pass"); + PyIfStatementTree pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); + assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); + assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); + } + } From 39918edf9123f007881dcbfeb0dc10fe27580a4a Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Mon, 19 Aug 2019 17:56:25 +0200 Subject: [PATCH 05/35] add missing license header --- .../python/api/tree/PyExpressionTree.java | 19 +++++++++++++++++++ .../python/tree/PyExpressionTreeImpl.java | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionTree.java index f03bf037da..2e918c6198 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionTree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionTree.java @@ -1,3 +1,22 @@ +/* + * 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.api.tree; public interface PyExpressionTree extends Tree { diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java index 557d1f8339..c5660c0afb 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java @@ -1,3 +1,22 @@ +/* + * 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.tree; import com.sonar.sslr.api.AstNode; From 2e997557cb83e29d2ec13fa2f17305f03ba8ddab Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Mon, 19 Aug 2019 18:54:35 +0200 Subject: [PATCH 06/35] PyIfStatementTree: convert elif and else in PythonTreeMaker --- .../python/api/tree/PythonTreeMaker.java | 25 +++++++-- .../python/tree/PyElseStatementTreeImpl.java | 53 +++++++++++++++++++ .../python/tree/PyExpressionTreeImpl.java | 10 +++- .../python/tree/PyIfStatementTreeImpl.java | 11 ++-- .../python/api/tree/PythonTreeMakerTest.java | 16 +++++- 5 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java index 158487e37a..e63683a501 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java @@ -21,10 +21,12 @@ import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; +import org.sonar.python.tree.PyElseStatementTreeImpl; import org.sonar.python.tree.PyExpressionTreeImpl; import org.sonar.python.tree.PyFileInputTreeImpl; import org.sonar.python.tree.PyIfStatementTreeImpl; @@ -49,16 +51,31 @@ public PyIfStatementTree ifStatement(AstNode astNode) { AstNode suite = astNode.getFirstChild(PythonGrammar.SUITE); List statements = suite.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); AstNode elseSuite = astNode.getLastChild(PythonGrammar.SUITE); - boolean hasElse = false; + PyElseStatementTree elseStatement = null; if (elseSuite.getPreviousSibling().getPreviousSibling().is(PythonKeyword.ELSE)) { - hasElse = true; + elseStatement = elseStatement(elseSuite); } + List elifBranches = astNode.getChildren(PythonKeyword.ELIF).stream() + .map(this::elifStatement) + .collect(Collectors.toList()); + return new PyIfStatementTreeImpl( - astNode, ifToken, expression(condition), statements); + astNode, ifToken, expression(condition), statements, elifBranches, elseStatement); + } + + private PyIfStatementTree elifStatement(AstNode astNode) { + Token elifToken = astNode.getToken(); + AstNode suite = astNode.getNextSibling().getNextSibling().getNextSibling(); + AstNode condition = astNode.getNextSibling(); + List statements = suite.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); + return new PyIfStatementTreeImpl( + astNode, elifToken, expression(condition), statements, Collections.emptyList(), null); } private PyElseStatementTree elseStatement(AstNode astNode) { - return null; + Token elseToken = astNode.getPreviousSibling().getPreviousSibling().getToken(); + List statements = astNode.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); + return new PyElseStatementTreeImpl(astNode, elseToken, statements); } PyExpressionTree expression(AstNode astNode) { diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java new file mode 100644 index 0000000000..27b19b36bc --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java @@ -0,0 +1,53 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTree; + +public class PyElseStatementTreeImpl extends PyTree implements PyElseStatementTree { + private final Token elseKeyword; + private final List body; + + public PyElseStatementTreeImpl(AstNode astNode, Token elseKeyword, List body) { + super(astNode); + this.elseKeyword = elseKeyword; + this.body = body; + } + + @Override + public Kind getKind() { + return null; + } + + @Override + public Token elseKeyword() { + return elseKeyword; + } + + @Override + public List body() { + return body; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java index c5660c0afb..c6abd4ab93 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java @@ -21,8 +21,16 @@ import com.sonar.sslr.api.AstNode; import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyTree; -public class PyExpressionTreeImpl implements PyExpressionTree { +public class PyExpressionTreeImpl extends PyTree implements PyExpressionTree { public PyExpressionTreeImpl(AstNode astNode) { + super(astNode); } + + @Override + public Kind getKind() { + return null; + } + } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java index ed89c92964..7ed4496605 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java @@ -34,12 +34,17 @@ public class PyIfStatementTreeImpl extends PyTree implements PyIfStatementTree { private final Token keyword; private final PyExpressionTree condition; private final List statements; + private final List elifBranches; + @CheckForNull + private final PyElseStatementTree elseStatement; - public PyIfStatementTreeImpl(AstNode node, Token keyword, PyExpressionTree condition, List statements) { + public PyIfStatementTreeImpl(AstNode node, Token keyword, PyExpressionTree condition, List statements, List elifBranches, @CheckForNull PyElseStatementTree elseStatement) { super(node); this.keyword = keyword; this.condition = condition; this.statements = statements; + this.elifBranches = elifBranches; + this.elseStatement = elseStatement; } @Override @@ -59,7 +64,7 @@ public List body() { @Override public List elifBranches() { - return null; + return elifBranches; } @Override @@ -70,7 +75,7 @@ public boolean isElif() { @CheckForNull @Override public PyElseStatementTree elseBranch() { - return null; + return elseStatement; } @Override diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index fcbe3eaa68..bd73bc67f6 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -36,12 +36,26 @@ public void fileInputTreeOnEmptyFile() { } @Test - public void fileInputTreeWithIfStatement() { + public void IfStatement() { setRootRule(PythonGrammar.IF_STMT); AstNode astNode = p.parse("if x: pass"); PyIfStatementTree pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); + + astNode = p.parse("if x: pass\nelse: pass"); + pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); + assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); + assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); + assertThat(pyIfStatementTree.elseBranch()).isNotNull(); + + astNode = p.parse("if x: pass\nelif y: pass"); + pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); + assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); + assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); + assertThat(pyIfStatementTree.elifBranches()).isNotEmpty(); + PyIfStatementTree elif = pyIfStatementTree.elifBranches().get(0); + assertThat(elif.keyword().getValue()).isEqualTo("elif"); } } From 963188d76aa41d326b1ca10fd1892a6bea0d0643 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 11:11:11 +0200 Subject: [PATCH 07/35] Get statements from STMT_LIST and STATEMENT, move PyTree and PythonTreeMaker, refactoring on PyIfStatementTree --- .../sonar/python/TestPythonVisitorRunner.java | 2 +- .../python/tree/PyElseStatementTreeImpl.java | 1 - .../python/tree/PyExpressionTreeImpl.java | 1 - .../python/tree/PyFileInputTreeImpl.java | 1 - .../python/tree/PyIfStatementTreeImpl.java | 29 +++++++++-- .../sonar/python/{api => }/tree/PyTree.java | 3 +- .../{api => }/tree/PythonTreeMaker.java | 48 +++++++++++++++---- .../python/api/tree/PythonTreeMakerTest.java | 35 ++++++++++++-- .../sonar/plugins/python/PythonScanner.java | 2 +- 9 files changed, 98 insertions(+), 24 deletions(-) rename python-squid/src/main/java/org/sonar/python/{api => }/tree/PyTree.java (94%) rename python-squid/src/main/java/org/sonar/python/{api => }/tree/PythonTreeMaker.java (58%) diff --git a/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java b/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java index 13a7a2ffc8..bd1798507a 100644 --- a/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java +++ b/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java @@ -26,7 +26,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import org.sonar.python.api.tree.PyFileInputTree; -import org.sonar.python.api.tree.PythonTreeMaker; +import org.sonar.python.tree.PythonTreeMaker; import org.sonar.python.parser.PythonParser; public class TestPythonVisitorRunner { diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java index 27b19b36bc..aa47805e04 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java @@ -24,7 +24,6 @@ import java.util.List; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyStatementTree; -import org.sonar.python.api.tree.PyTree; public class PyElseStatementTreeImpl extends PyTree implements PyElseStatementTree { private final Token elseKeyword; diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java index c6abd4ab93..6c563b9e19 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java @@ -21,7 +21,6 @@ import com.sonar.sslr.api.AstNode; import org.sonar.python.api.tree.PyExpressionTree; -import org.sonar.python.api.tree.PyTree; public class PyExpressionTreeImpl extends PyTree implements PyExpressionTree { public PyExpressionTreeImpl(AstNode astNode) { diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java index 3374a1fee2..eebb43ab3f 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java @@ -23,7 +23,6 @@ import java.util.List; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyStatementTree; -import org.sonar.python.api.tree.PyTree; import org.sonar.python.api.tree.Tree; public class PyFileInputTreeImpl extends PyTree implements PyFileInputTree { diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java index 7ed4496605..f19f057cd7 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java @@ -21,13 +21,13 @@ import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; +import java.util.Collections; import java.util.List; import javax.annotation.CheckForNull; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyStatementTree; -import org.sonar.python.api.tree.PyTree; public class PyIfStatementTreeImpl extends PyTree implements PyIfStatementTree { @@ -35,18 +35,37 @@ public class PyIfStatementTreeImpl extends PyTree implements PyIfStatementTree { private final PyExpressionTree condition; private final List statements; private final List elifBranches; + private final boolean isElif; @CheckForNull private final PyElseStatementTree elseStatement; - public PyIfStatementTreeImpl(AstNode node, Token keyword, PyExpressionTree condition, List statements, List elifBranches, @CheckForNull PyElseStatementTree elseStatement) { + /** + * + * If statement constructor + */ + public PyIfStatementTreeImpl(AstNode node, Token ifKeyword, PyExpressionTree condition, List statements, List elifBranches, @CheckForNull PyElseStatementTree elseStatement) { super(node); - this.keyword = keyword; + this.keyword = ifKeyword; this.condition = condition; this.statements = statements; this.elifBranches = elifBranches; + this.isElif = false; this.elseStatement = elseStatement; } + /** + * Elif statement constructor + */ + public PyIfStatementTreeImpl(AstNode node, Token elifKeyword, PyExpressionTree condition, List statements) { + super(node); + this.keyword = elifKeyword; + this.condition = condition; + this.statements = statements; + this.elifBranches = Collections.emptyList(); + this.isElif = true; + this.elseStatement = null; + } + @Override public Token keyword() { return keyword; @@ -59,7 +78,7 @@ public PyExpressionTree condition() { @Override public List body() { - return null; + return statements; } @Override @@ -69,7 +88,7 @@ public List elifBranches() { @Override public boolean isElif() { - return false; + return isElif; } @CheckForNull diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java b/python-squid/src/main/java/org/sonar/python/tree/PyTree.java similarity index 94% rename from python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java rename to python-squid/src/main/java/org/sonar/python/tree/PyTree.java index ece281d3d4..5aa3fcf00a 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTree.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyTree.java @@ -17,9 +17,10 @@ * 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.api.tree; +package org.sonar.python.tree; import com.sonar.sslr.api.AstNode; +import org.sonar.python.api.tree.Tree; public abstract class PyTree extends AstNode implements Tree { private final AstNode node; diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java similarity index 58% rename from python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java rename to python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index e63683a501..6922a705ac 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -17,7 +17,7 @@ * 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.api.tree; +package org.sonar.python.tree; import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; @@ -26,15 +26,16 @@ import java.util.stream.Collectors; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; -import org.sonar.python.tree.PyElseStatementTreeImpl; -import org.sonar.python.tree.PyExpressionTreeImpl; -import org.sonar.python.tree.PyFileInputTreeImpl; -import org.sonar.python.tree.PyIfStatementTreeImpl; +import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyStatementTree; public class PythonTreeMaker { public PyFileInputTree fileInput(AstNode astNode) { - List statements = astNode.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); + List statements = getStatements(astNode).stream().map(this::statement).collect(Collectors.toList()); return new PyFileInputTreeImpl(astNode, statements); } @@ -45,11 +46,38 @@ private PyStatementTree statement(AstNode astNode) { return null; } + private List getStatementsFromSuite(AstNode astNode) { + if (astNode.is(PythonGrammar.SUITE)) { + List statements = getStatements(astNode); + if (statements.isEmpty()) { + AstNode stmtListNode = astNode.getFirstChild(PythonGrammar.STMT_LIST); + return stmtListNode.getChildren(PythonGrammar.SIMPLE_STMT).stream() + .map(AstNode::getFirstChild) + .collect(Collectors.toList()); + } + return statements; + } + return Collections.emptyList(); + } + + private List getStatements(AstNode astNode) { + List statements = astNode.getChildren(PythonGrammar.STATEMENT); + return statements.stream().flatMap(stmt -> { + if (stmt.hasDirectChildren(PythonGrammar.STMT_LIST)) { + AstNode stmtListNode = stmt.getFirstChild(PythonGrammar.STMT_LIST); + return stmtListNode.getChildren(PythonGrammar.SIMPLE_STMT).stream() + .map(AstNode::getFirstChild); + } + return stmt.getChildren(PythonGrammar.COMPOUND_STMT).stream() + .map(AstNode::getFirstChild); + }).collect(Collectors.toList()); + } + public PyIfStatementTree ifStatement(AstNode astNode) { Token ifToken = astNode.getTokens().get(0); AstNode condition = astNode.getFirstChild(PythonGrammar.TEST); AstNode suite = astNode.getFirstChild(PythonGrammar.SUITE); - List statements = suite.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); + List statements = getStatementsFromSuite(suite).stream().map(this::statement).collect(Collectors.toList()); AstNode elseSuite = astNode.getLastChild(PythonGrammar.SUITE); PyElseStatementTree elseStatement = null; if (elseSuite.getPreviousSibling().getPreviousSibling().is(PythonKeyword.ELSE)) { @@ -67,14 +95,14 @@ private PyIfStatementTree elifStatement(AstNode astNode) { Token elifToken = astNode.getToken(); AstNode suite = astNode.getNextSibling().getNextSibling().getNextSibling(); AstNode condition = astNode.getNextSibling(); - List statements = suite.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); + List statements = getStatementsFromSuite(suite).stream().map(this::statement).collect(Collectors.toList()); return new PyIfStatementTreeImpl( - astNode, elifToken, expression(condition), statements, Collections.emptyList(), null); + astNode, elifToken, expression(condition), statements); } private PyElseStatementTree elseStatement(AstNode astNode) { Token elseToken = astNode.getPreviousSibling().getPreviousSibling().getToken(); - List statements = astNode.getChildren(PythonGrammar.STATEMENT).stream().map(this::statement).collect(Collectors.toList()); + List statements = getStatementsFromSuite(astNode).stream().map(this::statement).collect(Collectors.toList()); return new PyElseStatementTreeImpl(astNode, elseToken, statements); } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index bd73bc67f6..d2152f0c0d 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.sonar.python.api.PythonGrammar; import org.sonar.python.parser.RuleTest; +import org.sonar.python.tree.PythonTreeMaker; import static org.assertj.core.api.Assertions.assertThat; @@ -33,6 +34,10 @@ public void fileInputTreeOnEmptyFile() { AstNode astNode = p.parse(""); PyFileInputTree pyTree = new PythonTreeMaker().fileInput(astNode); assertThat(pyTree.statements()).isEmpty(); + + astNode = p.parse("pass"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); } @Test @@ -42,20 +47,44 @@ public void IfStatement() { PyIfStatementTree pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); + assertThat(pyIfStatementTree.isElif()).isFalse(); + assertThat(pyIfStatementTree.elifBranches()).isEmpty(); + assertThat(pyIfStatementTree.elseBranch()).isNull(); + assertThat(pyIfStatementTree.body()).hasSize(1); astNode = p.parse("if x: pass\nelse: pass"); pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); - assertThat(pyIfStatementTree.elseBranch()).isNotNull(); + assertThat(pyIfStatementTree.isElif()).isFalse(); + assertThat(pyIfStatementTree.elifBranches()).isEmpty(); + PyElseStatementTree elseBranch = pyIfStatementTree.elseBranch(); + assertThat(elseBranch).isNotNull(); + assertThat(elseBranch.elseKeyword().getValue()).isEqualTo("else"); + assertThat(elseBranch.body()).hasSize(1); astNode = p.parse("if x: pass\nelif y: pass"); pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); - assertThat(pyIfStatementTree.elifBranches()).isNotEmpty(); + assertThat(pyIfStatementTree.isElif()).isFalse(); + assertThat(pyIfStatementTree.elseBranch()).isNull(); + assertThat(pyIfStatementTree.elifBranches()).hasSize(1); PyIfStatementTree elif = pyIfStatementTree.elifBranches().get(0); - assertThat(elif.keyword().getValue()).isEqualTo("elif"); + assertThat(elif.condition()).isInstanceOf(PyExpressionTree.class); + assertThat(elif.isElif()).isTrue(); + assertThat(elif.elseBranch()).isNull(); + assertThat(elif.elifBranches()).isEmpty(); + assertThat(elif.body()).hasSize(1); + + astNode = p.parse("if x:\n pass"); + pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); + assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); + assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); + assertThat(pyIfStatementTree.isElif()).isFalse(); + assertThat(pyIfStatementTree.elseBranch()).isNull(); + assertThat(pyIfStatementTree.elifBranches()).isEmpty(); + assertThat(pyIfStatementTree.body()).hasSize(1); } } 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 667aba34ec..5d0ca74e2f 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 @@ -46,7 +46,7 @@ import org.sonar.python.PythonFile; import org.sonar.python.PythonVisitorContext; import org.sonar.python.api.tree.PyFileInputTree; -import org.sonar.python.api.tree.PythonTreeMaker; +import org.sonar.python.tree.PythonTreeMaker; import org.sonar.python.metrics.FileLinesVisitor; import org.sonar.python.metrics.FileMetrics; import org.sonar.python.parser.PythonParser; From 6cf247db6f00705fdbf50288ba82592d18ae220c Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 11:36:33 +0200 Subject: [PATCH 08/35] add PyPrintStatementTree --- .../python/api/tree/PyPrintStatementTree.java | 28 ++++++++++ .../java/org/sonar/python/api/tree/Tree.java | 4 +- .../python/tree/PyPrintStatementTreeImpl.java | 52 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 10 ++++ .../python/api/tree/PythonTreeMakerTest.java | 21 ++++++++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyPrintStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyPrintStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyPrintStatementTree.java new file mode 100644 index 0000000000..344625a88a --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyPrintStatementTree.java @@ -0,0 +1,28 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyPrintStatementTree extends Tree { + Token printKeyword(); + List expression(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 910eaf08c2..9415e11c73 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -22,7 +22,9 @@ public interface Tree { enum Kind { - FILE_INPUT(PyFileInputTree.class); + FILE_INPUT(PyFileInputTree.class), + + PRINT_STMT(PyPrintStatementTree.class); final Class associatedInterface; diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java new file mode 100644 index 0000000000..4c1f3506bd --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java @@ -0,0 +1,52 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyPrintStatementTree; + +public class PyPrintStatementTreeImpl extends PyTree implements PyPrintStatementTree { + private final Token printKeyword; + private final List expressions; + + public PyPrintStatementTreeImpl(AstNode astNode, Token printKeyword, List expressions) { + super(astNode); + this.printKeyword = printKeyword; + this.expressions = expressions; + } + + @Override + public Token printKeyword() { + return printKeyword; + } + + @Override + public List expression() { + return expressions; + } + + @Override + public Kind getKind() { + return Kind.PRINT_STMT; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 6922a705ac..a09178a4e8 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -30,6 +30,7 @@ import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyPrintStatementTree; import org.sonar.python.api.tree.PyStatementTree; public class PythonTreeMaker { @@ -73,6 +74,15 @@ private List getStatements(AstNode astNode) { }).collect(Collectors.toList()); } + // Simple statements + + public PyPrintStatementTree printStatement(AstNode astNode) { + List expressions = astNode.getChildren(PythonGrammar.TEST).stream().map(this::expression).collect(Collectors.toList()); + return new PyPrintStatementTreeImpl(astNode, astNode.getTokens().get(0), expressions); + } + + // Compound statements + public PyIfStatementTree ifStatement(AstNode astNode) { Token ifToken = astNode.getTokens().get(0); AstNode condition = astNode.getFirstChild(PythonGrammar.TEST); diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index d2152f0c0d..e06ff5806b 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -87,4 +87,25 @@ public void IfStatement() { assertThat(pyIfStatementTree.body()).hasSize(1); } + @Test + public void printStatement() { + setRootRule(PythonGrammar.PRINT_STMT); + AstNode astNode = p.parse("print 'foo'"); + PyPrintStatementTree printStmt = new PythonTreeMaker().printStatement(astNode); + assertThat(printStmt).isNotNull(); + assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); + assertThat(printStmt.expression()).hasSize(1); + + astNode = p.parse("print 'foo', 'bar'"); + printStmt = new PythonTreeMaker().printStatement(astNode); + assertThat(printStmt).isNotNull(); + assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); + assertThat(printStmt.expression()).hasSize(2); + + astNode = p.parse("print >> 'foo'"); + printStmt = new PythonTreeMaker().printStatement(astNode); + assertThat(printStmt).isNotNull(); + assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); + assertThat(printStmt.expression()).hasSize(1); + } } From db997bcbb42884d84bcfefc15ae9e09b76802cd5 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 13:28:14 +0200 Subject: [PATCH 09/35] Add PyExecStatementTree --- .../python/api/tree/PyExecStatementTree.java | 32 ++++++++ .../python/api/tree/PyPrintStatementTree.java | 6 +- .../java/org/sonar/python/api/tree/Tree.java | 3 + .../python/tree/PyExecStatementTreeImpl.java | 73 +++++++++++++++++++ .../python/tree/PyPrintStatementTreeImpl.java | 2 +- .../sonar/python/tree/PythonTreeMaker.java | 12 ++- .../python/api/tree/PythonTreeMakerTest.java | 36 ++++++++- 7 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyExecStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyExecStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyExecStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyExecStatementTree.java new file mode 100644 index 0000000000..50eae73b8a --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyExecStatementTree.java @@ -0,0 +1,32 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; + +public interface PyExecStatementTree extends PyStatementTree { + Token execKeyword(); + + PyExpressionTree expression(); + + PyExpressionTree globalsExpression(); + + PyExpressionTree localsExpression(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyPrintStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyPrintStatementTree.java index 344625a88a..3ac93c6290 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyPrintStatementTree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyPrintStatementTree.java @@ -22,7 +22,9 @@ import com.sonar.sslr.api.Token; import java.util.List; -public interface PyPrintStatementTree extends Tree { +public interface PyPrintStatementTree extends PyStatementTree { + Token printKeyword(); - List expression(); + + List expressions(); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 9415e11c73..a4b9bfeb97 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -22,6 +22,9 @@ public interface Tree { enum Kind { + + EXEC_STMT(PyExecStatementTree.class), + FILE_INPUT(PyFileInputTree.class), PRINT_STMT(PyPrintStatementTree.class); diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExecStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExecStatementTreeImpl.java new file mode 100644 index 0000000000..f59cdb308a --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExecStatementTreeImpl.java @@ -0,0 +1,73 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import org.sonar.python.api.tree.PyExecStatementTree; +import org.sonar.python.api.tree.PyExpressionTree; + +public class PyExecStatementTreeImpl extends PyTree implements PyExecStatementTree { + private final Token execKeyword; + private final PyExpressionTree expression; + private final PyExpressionTree globalsExpression; + private final PyExpressionTree localsExpression; + + public PyExecStatementTreeImpl(AstNode astNode, Token execKeyword, PyExpressionTree expression, PyExpressionTree globalsExpression, PyExpressionTree localsExpression) { + super(astNode); + this.execKeyword = execKeyword; + this.expression = expression; + this.globalsExpression = globalsExpression; + this.localsExpression = localsExpression; + } + + public PyExecStatementTreeImpl(AstNode astNode, Token execKeyword, PyExpressionTree expression) { + super(astNode); + this.execKeyword = execKeyword; + this.expression = expression; + globalsExpression = null; + localsExpression = null; + } + + @Override + public Token execKeyword() { + return execKeyword; + } + + @Override + public PyExpressionTree expression() { + return expression; + } + + @Override + public PyExpressionTree globalsExpression() { + return globalsExpression; + } + + @Override + public PyExpressionTree localsExpression() { + return localsExpression; + } + + @Override + public Kind getKind() { + return Kind.EXEC_STMT; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java index 4c1f3506bd..332247682e 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java @@ -41,7 +41,7 @@ public Token printKeyword() { } @Override - public List expression() { + public List expressions() { return expressions; } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index a09178a4e8..cb79212e13 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -27,6 +27,7 @@ import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyIfStatementTree; @@ -81,6 +82,15 @@ public PyPrintStatementTree printStatement(AstNode astNode) { return new PyPrintStatementTreeImpl(astNode, astNode.getTokens().get(0), expressions); } + public PyExecStatementTree execStatement(AstNode astNode) { + PyExpressionTree expression = expression(astNode.getFirstChild(PythonGrammar.EXPR)); + List expressions = astNode.getChildren(PythonGrammar.TEST).stream().map(this::expression).collect(Collectors.toList()); + if (expressions.isEmpty()) { + return new PyExecStatementTreeImpl(astNode, astNode.getTokens().get(0), expression); + } + return new PyExecStatementTreeImpl(astNode, astNode.getTokens().get(0), expression, expressions.get(0), expressions.size() == 2 ? expressions.get(1) : null); + } + // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { @@ -119,6 +129,4 @@ private PyElseStatementTree elseStatement(AstNode astNode) { PyExpressionTree expression(AstNode astNode) { return new PyExpressionTreeImpl(astNode); } - - } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index e06ff5806b..424f9e407b 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -94,18 +94,48 @@ public void printStatement() { PyPrintStatementTree printStmt = new PythonTreeMaker().printStatement(astNode); assertThat(printStmt).isNotNull(); assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); - assertThat(printStmt.expression()).hasSize(1); + assertThat(printStmt.expressions()).hasSize(1); astNode = p.parse("print 'foo', 'bar'"); printStmt = new PythonTreeMaker().printStatement(astNode); assertThat(printStmt).isNotNull(); assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); - assertThat(printStmt.expression()).hasSize(2); + assertThat(printStmt.expressions()).hasSize(2); astNode = p.parse("print >> 'foo'"); printStmt = new PythonTreeMaker().printStatement(astNode); assertThat(printStmt).isNotNull(); assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); - assertThat(printStmt.expression()).hasSize(1); + assertThat(printStmt.expressions()).hasSize(1); + } + + @Test + public void execStatement() { + setRootRule(PythonGrammar.EXEC_STMT); + AstNode astNode = p.parse("exec 'foo'"); + PyExecStatementTree execStatement = new PythonTreeMaker().execStatement(astNode); + assertThat(execStatement).isNotNull(); + assertThat(execStatement.execKeyword().getValue()).isEqualTo("exec"); + assertThat(execStatement.expression()).isNotNull(); + assertThat(execStatement.globalsExpression()).isNull(); + assertThat(execStatement.localsExpression()).isNull(); + + astNode = p.parse("exec 'foo' in globals"); + execStatement = new PythonTreeMaker().execStatement(astNode); + assertThat(execStatement).isNotNull(); + assertThat(execStatement.execKeyword().getValue()).isEqualTo("exec"); + assertThat(execStatement.expression()).isNotNull(); + assertThat(execStatement.globalsExpression()).isNotNull(); + assertThat(execStatement.localsExpression()).isNull(); + + astNode = p.parse("exec 'foo' in globals, locals"); + execStatement = new PythonTreeMaker().execStatement(astNode); + assertThat(execStatement).isNotNull(); + assertThat(execStatement.execKeyword().getValue()).isEqualTo("exec"); + assertThat(execStatement.expression()).isNotNull(); + assertThat(execStatement.globalsExpression()).isNotNull(); + assertThat(execStatement.localsExpression()).isNotNull(); + + // TODO: exec stmt should parse exec ('foo', globals, locals); see https://docs.python.org/2/reference/simple_stmts.html#exec } } From f7e9a2c2529ad5856a23fbb75377e601907379f4 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 13:46:17 +0200 Subject: [PATCH 10/35] Add PyAssertStatementTree --- .../api/tree/PyAssertStatementTree.java | 29 +++++++++++ .../java/org/sonar/python/api/tree/Tree.java | 2 + .../tree/PyAssertStatementTreeImpl.java | 52 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 7 +++ .../python/api/tree/PythonTreeMakerTest.java | 16 ++++++ 5 files changed, 106 insertions(+) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyAssertStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyAssertStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyAssertStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyAssertStatementTree.java new file mode 100644 index 0000000000..e29ec0b229 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyAssertStatementTree.java @@ -0,0 +1,29 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyAssertStatementTree extends PyStatementTree { + Token assertKeyword(); + + List expressions(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index a4b9bfeb97..2ef107b924 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -23,6 +23,8 @@ public interface Tree { enum Kind { + ASSERT_STMT(PyAssertStatementTree.class), + EXEC_STMT(PyExecStatementTree.class), FILE_INPUT(PyFileInputTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyAssertStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyAssertStatementTreeImpl.java new file mode 100644 index 0000000000..eaaa591c8b --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyAssertStatementTreeImpl.java @@ -0,0 +1,52 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyAssertStatementTree; +import org.sonar.python.api.tree.PyExpressionTree; + +public class PyAssertStatementTreeImpl extends PyTree implements PyAssertStatementTree { + private final Token assertKeyword; + private final List expressions; + + public PyAssertStatementTreeImpl(AstNode astNode, Token assertKeyword, List expressions) { + super(astNode); + this.assertKeyword = assertKeyword; + this.expressions = expressions; + } + + @Override + public Token assertKeyword() { + return assertKeyword; + } + + @Override + public List expressions() { + return expressions; + } + + @Override + public Kind getKind() { + return Kind.ASSERT_STMT; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index cb79212e13..4d0e20fe02 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; +import org.sonar.python.api.tree.PyAssertStatementTree; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionTree; @@ -91,6 +92,12 @@ public PyExecStatementTree execStatement(AstNode astNode) { return new PyExecStatementTreeImpl(astNode, astNode.getTokens().get(0), expression, expressions.get(0), expressions.size() == 2 ? expressions.get(1) : null); } + + public PyAssertStatementTree assertStatement(AstNode astNode) { + List expressions = astNode.getChildren(PythonGrammar.TEST).stream().map(this::expression).collect(Collectors.toList()); + return new PyAssertStatementTreeImpl(astNode, astNode.getTokens().get(0), expressions); + } + // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 424f9e407b..f3a0ba9b51 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -138,4 +138,20 @@ public void execStatement() { // TODO: exec stmt should parse exec ('foo', globals, locals); see https://docs.python.org/2/reference/simple_stmts.html#exec } + + @Test + public void assertStatement() { + setRootRule(PythonGrammar.ASSERT_STMT); + AstNode astNode = p.parse("assert x"); + PyAssertStatementTree assertStatement = new PythonTreeMaker().assertStatement(astNode); + assertThat(assertStatement).isNotNull(); + assertThat(assertStatement.assertKeyword().getValue()).isEqualTo("assert"); + assertThat(assertStatement.expressions()).hasSize(1); + + astNode = p.parse("assert x, y"); + assertStatement = new PythonTreeMaker().assertStatement(astNode); + assertThat(assertStatement).isNotNull(); + assertThat(assertStatement.assertKeyword().getValue()).isEqualTo("assert"); + assertThat(assertStatement.expressions()).hasSize(2); + } } From 965b06914e82352bff29e7ca94dad708c495933d Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 13:56:16 +0200 Subject: [PATCH 11/35] Add PyPassStatementTree --- .../python/api/tree/PyPassStatementTree.java | 26 +++++++++++ .../java/org/sonar/python/api/tree/Tree.java | 2 + .../python/tree/PyPassStatementTreeImpl.java | 43 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 6 +++ .../python/api/tree/PythonTreeMakerTest.java | 9 ++++ 5 files changed, 86 insertions(+) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyPassStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyPassStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyPassStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyPassStatementTree.java new file mode 100644 index 0000000000..d256d2688c --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyPassStatementTree.java @@ -0,0 +1,26 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; + +public interface PyPassStatementTree extends PyStatementTree { + Token passKeyword(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 2ef107b924..946aaded9b 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -29,6 +29,8 @@ enum Kind { FILE_INPUT(PyFileInputTree.class), + PASS_STMT(PyPassStatementTree.class), + PRINT_STMT(PyPrintStatementTree.class); final Class associatedInterface; diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyPassStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyPassStatementTreeImpl.java new file mode 100644 index 0000000000..01798a7cc3 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyPassStatementTreeImpl.java @@ -0,0 +1,43 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import org.sonar.python.api.tree.PyPassStatementTree; + +public class PyPassStatementTreeImpl extends PyTree implements PyPassStatementTree { + private final Token passKeyword; + + public PyPassStatementTreeImpl(AstNode astNode, Token passKeyword) { + super(astNode); + this.passKeyword = passKeyword; + } + + @Override + public Token passKeyword() { + return passKeyword; + } + + @Override + public Kind getKind() { + return Kind.PASS_STMT; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 4d0e20fe02..23d3c4d30f 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -32,6 +32,7 @@ import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; import org.sonar.python.api.tree.PyStatementTree; @@ -98,6 +99,11 @@ public PyAssertStatementTree assertStatement(AstNode astNode) { return new PyAssertStatementTreeImpl(astNode, astNode.getTokens().get(0), expressions); } + + public PyPassStatementTree passStatement(AstNode astNode) { + return new PyPassStatementTreeImpl(astNode, astNode.getTokens().get(0)); + } + // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index f3a0ba9b51..33b44a3330 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -154,4 +154,13 @@ public void assertStatement() { assertThat(assertStatement.assertKeyword().getValue()).isEqualTo("assert"); assertThat(assertStatement.expressions()).hasSize(2); } + + @Test + public void passStatement() { + setRootRule(PythonGrammar.PASS_STMT); + AstNode astNode = p.parse("pass"); + PyPassStatementTree passStatement = new PythonTreeMaker().passStatement(astNode); + assertThat(passStatement).isNotNull(); + assertThat(passStatement.passKeyword().getValue()).isEqualTo("pass"); + } } From 757bd16a4a264ba48a958059a9efc511d89886f3 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 14:20:15 +0200 Subject: [PATCH 12/35] Add PyDelStatementTree --- .../python/api/tree/PyDelStatementTree.java | 29 +++++++++++ .../java/org/sonar/python/api/tree/Tree.java | 2 + .../python/tree/PyDelStatementTreeImpl.java | 52 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 10 ++++ .../python/api/tree/PythonTreeMakerTest.java | 22 ++++++++ 5 files changed, 115 insertions(+) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyDelStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyDelStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyDelStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyDelStatementTree.java new file mode 100644 index 0000000000..f07fc79c55 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyDelStatementTree.java @@ -0,0 +1,29 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyDelStatementTree extends PyStatementTree { + Token delKeyword(); + + List expressions(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 946aaded9b..fdc9eb1a59 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -25,6 +25,8 @@ enum Kind { ASSERT_STMT(PyAssertStatementTree.class), + DEL_STMT(PyDelStatementTree.class), + EXEC_STMT(PyExecStatementTree.class), FILE_INPUT(PyFileInputTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyDelStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyDelStatementTreeImpl.java new file mode 100644 index 0000000000..9eaf1d3bac --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyDelStatementTreeImpl.java @@ -0,0 +1,52 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyDelStatementTree; +import org.sonar.python.api.tree.PyExpressionTree; + +public class PyDelStatementTreeImpl extends PyTree implements PyDelStatementTree { + private final Token delKeyword; + private final List expressionTrees; + + public PyDelStatementTreeImpl(AstNode astNode, Token delKeyword, List expressionTrees) { + super(astNode); + this.delKeyword = delKeyword; + this.expressionTrees = expressionTrees; + } + + @Override + public Token delKeyword() { + return delKeyword; + } + + @Override + public List expressions() { + return expressionTrees; + } + + @Override + public Kind getKind() { + return Kind.DEL_STMT; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 23d3c4d30f..ad054178ad 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -27,6 +27,7 @@ import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; import org.sonar.python.api.tree.PyAssertStatementTree; +import org.sonar.python.api.tree.PyDelStatementTree; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionTree; @@ -104,6 +105,15 @@ public PyPassStatementTree passStatement(AstNode astNode) { return new PyPassStatementTreeImpl(astNode, astNode.getTokens().get(0)); } + + public PyDelStatementTree delStatement(AstNode astNode) { + AstNode exprListNode = astNode.getFirstChild(PythonGrammar.EXPRLIST); + List expressionTrees = exprListNode.getChildren(PythonGrammar.EXPR, PythonGrammar.STAR_EXPR).stream() + .map(this::expression) + .collect(Collectors.toList()); + return new PyDelStatementTreeImpl(astNode, astNode.getTokens().get(0), expressionTrees); + } + // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 33b44a3330..6610cde932 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -163,4 +163,26 @@ public void passStatement() { assertThat(passStatement).isNotNull(); assertThat(passStatement.passKeyword().getValue()).isEqualTo("pass"); } + + @Test + public void delStatement() { + setRootRule(PythonGrammar.DEL_STMT); + AstNode astNode = p.parse("del foo"); + PyDelStatementTree passStatement = new PythonTreeMaker().delStatement(astNode); + assertThat(passStatement).isNotNull(); + assertThat(passStatement.delKeyword().getValue()).isEqualTo("del"); + assertThat(passStatement.expressions()).hasSize(1); + + astNode = p.parse("del foo, bar"); + passStatement = new PythonTreeMaker().delStatement(astNode); + assertThat(passStatement).isNotNull(); + assertThat(passStatement.delKeyword().getValue()).isEqualTo("del"); + assertThat(passStatement.expressions()).hasSize(2); + + astNode = p.parse("del *foo"); + passStatement = new PythonTreeMaker().delStatement(astNode); + assertThat(passStatement).isNotNull(); + assertThat(passStatement.delKeyword().getValue()).isEqualTo("del"); + assertThat(passStatement.expressions()).hasSize(1); + } } From 1d1bab3cc4fe6d110a47a3b08bf0eea6422dcc5e Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 14:55:41 +0200 Subject: [PATCH 13/35] add PyReturnStatementTree, dispatch statements conversion --- .../api/tree/PyReturnStatementTree.java | 29 +++++++++++ .../java/org/sonar/python/api/tree/Tree.java | 4 +- .../tree/PyReturnStatementTreeImpl.java | 52 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 34 ++++++++++-- .../python/api/tree/PythonTreeMakerTest.java | 48 +++++++++++++++++ 5 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyReturnStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyReturnStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyReturnStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyReturnStatementTree.java new file mode 100644 index 0000000000..5af5a8e26a --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyReturnStatementTree.java @@ -0,0 +1,29 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyReturnStatementTree extends PyStatementTree { + Token returnKeyword(); + + List expressions(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index fdc9eb1a59..1a4f6e5995 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -33,7 +33,9 @@ enum Kind { PASS_STMT(PyPassStatementTree.class), - PRINT_STMT(PyPrintStatementTree.class); + PRINT_STMT(PyPrintStatementTree.class), + + RETURN_STMT(PyReturnStatementTree.class); final Class associatedInterface; diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyReturnStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyReturnStatementTreeImpl.java new file mode 100644 index 0000000000..1576c6fee2 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyReturnStatementTreeImpl.java @@ -0,0 +1,52 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyReturnStatementTree; + +public class PyReturnStatementTreeImpl extends PyTree implements PyReturnStatementTree { + private final Token returnKeyword; + private final List expressionTrees; + + public PyReturnStatementTreeImpl(AstNode astNode, Token returnKeyword, List expressionTrees) { + super(astNode); + this.returnKeyword = returnKeyword; + this.expressionTrees = expressionTrees; + } + + @Override + public Token returnKeyword() { + return returnKeyword; + } + + @Override + public List expressions() { + return expressionTrees; + } + + @Override + public Kind getKind() { + return Kind.RETURN_STMT; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index ad054178ad..aa644a30dc 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -35,6 +35,7 @@ import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; +import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyStatementTree; public class PythonTreeMaker { @@ -48,6 +49,25 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.IF_STMT)) { return ifStatement(astNode); } + if (astNode.is(PythonGrammar.PRINT_STMT)) { + return printStatement(astNode); + } + if (astNode.is(PythonGrammar.EXEC_STMT)) { + return execStatement(astNode); + } + if (astNode.is(PythonGrammar.ASSERT_STMT)) { + return assertStatement(astNode); + } + if (astNode.is(PythonGrammar.PASS_STMT)) { + return passStatement(astNode); + } + if (astNode.is(PythonGrammar.DEL_STMT)) { + return delStatement(astNode); + } + if (astNode.is(PythonGrammar.RETURN_STMT)) { + return returnStatement(astNode); + } + // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -94,18 +114,15 @@ public PyExecStatementTree execStatement(AstNode astNode) { return new PyExecStatementTreeImpl(astNode, astNode.getTokens().get(0), expression, expressions.get(0), expressions.size() == 2 ? expressions.get(1) : null); } - public PyAssertStatementTree assertStatement(AstNode astNode) { List expressions = astNode.getChildren(PythonGrammar.TEST).stream().map(this::expression).collect(Collectors.toList()); return new PyAssertStatementTreeImpl(astNode, astNode.getTokens().get(0), expressions); } - public PyPassStatementTree passStatement(AstNode astNode) { return new PyPassStatementTreeImpl(astNode, astNode.getTokens().get(0)); } - public PyDelStatementTree delStatement(AstNode astNode) { AstNode exprListNode = astNode.getFirstChild(PythonGrammar.EXPRLIST); List expressionTrees = exprListNode.getChildren(PythonGrammar.EXPR, PythonGrammar.STAR_EXPR).stream() @@ -114,6 +131,17 @@ public PyDelStatementTree delStatement(AstNode astNode) { return new PyDelStatementTreeImpl(astNode, astNode.getTokens().get(0), expressionTrees); } + public PyReturnStatementTree returnStatement(AstNode astNode) { + AstNode testListNode = astNode.getFirstChild(PythonGrammar.TESTLIST); + List expressionTrees = Collections.emptyList(); + if (testListNode != null) { + expressionTrees = testListNode.getChildren(PythonGrammar.TEST).stream() + .map(this::expression) + .collect(Collectors.toList()); + } + return new PyReturnStatementTreeImpl(astNode, astNode.getTokens().get(0), expressionTrees); + } + // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 6610cde932..fc18a533da 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -38,6 +38,32 @@ public void fileInputTreeOnEmptyFile() { astNode = p.parse("pass"); pyTree = new PythonTreeMaker().fileInput(astNode); assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyPassStatementTree.class); + + astNode = p.parse("print 'foo'"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyPrintStatementTree.class); + + astNode = p.parse("exec foo"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyExecStatementTree.class); + + astNode = p.parse("assert foo"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyAssertStatementTree.class); + + astNode = p.parse("del foo"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyDelStatementTree.class); + + astNode = p.parse("return foo"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyReturnStatementTree.class); } @Test @@ -185,4 +211,26 @@ public void delStatement() { assertThat(passStatement.delKeyword().getValue()).isEqualTo("del"); assertThat(passStatement.expressions()).hasSize(1); } + + @Test + public void returnStatement() { + setRootRule(PythonGrammar.RETURN_STMT); + AstNode astNode = p.parse("return foo"); + PyReturnStatementTree returnStatement = new PythonTreeMaker().returnStatement(astNode); + assertThat(returnStatement).isNotNull(); + assertThat(returnStatement.returnKeyword().getValue()).isEqualTo("return"); + assertThat(returnStatement.expressions()).hasSize(1); + + astNode = p.parse("return foo, bar"); + returnStatement = new PythonTreeMaker().returnStatement(astNode); + assertThat(returnStatement).isNotNull(); + assertThat(returnStatement.returnKeyword().getValue()).isEqualTo("return"); + assertThat(returnStatement.expressions()).hasSize(2); + + astNode = p.parse("return"); + returnStatement = new PythonTreeMaker().returnStatement(astNode); + assertThat(returnStatement).isNotNull(); + assertThat(returnStatement.returnKeyword().getValue()).isEqualTo("return"); + assertThat(returnStatement.expressions()).hasSize(0); + } } From f4854ae9828932c7854a9ed44719a20dc6370841 Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Tue, 20 Aug 2019 15:11:33 +0200 Subject: [PATCH 14/35] Migrate Collapsible ifStatement to strongly typed Triggers refactoring on scanner is executing checks and types of checks --- .../checks/AbstractCallExpressionCheck.java | 4 +- .../python/checks/AbstractNameCheck.java | 4 +- .../checks/AfterJumpStatementCheck.java | 4 +- .../python/checks/BackslashInStringCheck.java | 4 +- .../python/checks/BackticksUsageCheck.java | 4 +- .../checks/BreakContinueOutsideLoopCheck.java | 4 +- .../org/sonar/python/checks/CheckList.java | 8 +- .../python/checks/ClassComplexityCheck.java | 4 +- .../CognitiveComplexityFunctionCheck.java | 4 +- .../checks/CollapsibleIfStatementsCheck.java | 64 +++++------ .../checks/CommentRegularExpressionCheck.java | 4 +- .../python/checks/CommentedCodeCheck.java | 4 +- .../DuplicatedMethodFieldNamesCheck.java | 4 +- .../python/checks/EmptyNestedBlockCheck.java | 4 +- .../checks/ExecStatementUsageCheck.java | 4 +- .../checks/ExitHasBadArgumentsCheck.java | 4 +- .../checks/FieldDuplicatesClassNameCheck.java | 4 +- .../sonar/python/checks/FieldNameCheck.java | 4 +- .../python/checks/FileComplexityCheck.java | 4 +- .../python/checks/FixmeCommentCheck.java | 4 +- .../checks/FunctionComplexityCheck.java | 4 +- .../sonar/python/checks/HardcodedIPCheck.java | 4 +- ...nticalExpressionOnBinaryOperatorCheck.java | 4 +- .../python/checks/InequalityUsageCheck.java | 4 +- .../python/checks/InitReturnsValueCheck.java | 4 +- .../sonar/python/checks/LineLengthCheck.java | 4 +- ...riableAndParameterNameConventionCheck.java | 4 +- ...gIntegerWithLowercaseSuffixUsageCheck.java | 4 +- .../checks/MethodShouldBeStaticCheck.java | 4 +- .../python/checks/MissingDocstringCheck.java | 4 +- .../MissingNewlineAtEndOfFileCheck.java | 4 +- .../sonar/python/checks/ModuleNameCheck.java | 4 +- .../python/checks/NeedlessPassCheck.java | 4 +- .../checks/NestedControlFlowDepthCheck.java | 4 +- .../python/checks/NewStyleClassCheck.java | 4 +- .../checks/NoPersonReferenceInTodoCheck.java | 4 +- .../checks/OneStatementPerLineCheck.java | 4 +- .../python/checks/ParsingErrorCheck.java | 4 +- .../checks/PreIncrementDecrementCheck.java | 4 +- .../checks/PrintStatementUsageCheck.java | 4 +- .../ReturnAndYieldInOneFunctionCheck.java | 4 +- .../ReturnYieldOutsideFunctionCheck.java | 4 +- .../sonar/python/checks/SameBranchCheck.java | 4 +- .../python/checks/SameConditionCheck.java | 4 +- .../python/checks/SelfAssignmentCheck.java | 4 +- .../checks/TooManyLinesInFileCheck.java | 4 +- .../python/checks/TooManyParametersCheck.java | 4 +- .../python/checks/TooManyReturnsCheck.java | 4 +- .../python/checks/TrailingCommentCheck.java | 4 +- .../checks/TrailingWhitespaceCheck.java | 4 +- .../checks/UnusedLocalVariableCheck.java | 4 +- .../UselessParenthesisAfterKeywordCheck.java | 4 +- .../checks/UselessParenthesisCheck.java | 4 +- .../org/sonar/python/checks/XPathCheck.java | 4 +- .../checks/hotspots/DebugModeCheck.java | 4 +- .../hotspots/DynamicCodeExecutionCheck.java | 4 +- .../python/checks/hotspots/RegexCheck.java | 4 +- .../sonar/python/checks/CheckUtilsTest.java | 8 +- .../java/org/sonar/python/IssueLocation.java | 10 ++ .../java/org/sonar/python/PythonCheck.java | 42 +------ .../org/sonar/python/PythonCheckAstNode.java | 63 +++++++++++ .../org/sonar/python/PythonCheckTree.java | 60 ++++++++++ .../java/org/sonar/python/PythonVisitor.java | 2 +- .../sonar/python/PythonVisitorContext.java | 5 +- .../sonar/python/api/tree/PyTreeVisitor.java | 41 +++++++ .../java/org/sonar/python/api/tree/Tree.java | 16 ++- .../org/sonar/python/metrics/FileMetrics.java | 2 +- .../sonar/python/tree/BaseTreeVisitor.java | 104 ++++++++++++++++++ .../tree/PyAssertStatementTreeImpl.java | 6 + .../python/tree/PyDelStatementTreeImpl.java | 6 + .../python/tree/PyElseStatementTreeImpl.java | 9 +- .../python/tree/PyExecStatementTreeImpl.java | 6 + .../python/tree/PyExpressionTreeImpl.java | 6 + .../python/tree/PyFileInputTreeImpl.java | 6 + .../python/tree/PyIfStatementTreeImpl.java | 9 +- .../python/tree/PyPassStatementTreeImpl.java | 6 + .../python/tree/PyPrintStatementTreeImpl.java | 6 + .../tree/PyReturnStatementTreeImpl.java | 6 + .../java/org/sonar/python/tree/PyTree.java | 10 ++ .../sonar/python/tree/PythonTreeMaker.java | 2 + .../org/sonar/python/PythonCheckTest.java | 4 +- .../plugins/python/PythonHighlighter.java | 4 +- .../sonar/plugins/python/PythonScanner.java | 1 + .../plugins/python/cpd/PythonCpdAnalyzer.java | 2 +- 84 files changed, 531 insertions(+), 203 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/PythonCheckAstNode.java create mode 100644 python-squid/src/main/java/org/sonar/python/PythonCheckTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java diff --git a/python-checks/src/main/java/org/sonar/python/checks/AbstractCallExpressionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/AbstractCallExpressionCheck.java index 41eea95b0a..adf8502ce9 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/AbstractCallExpressionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/AbstractCallExpressionCheck.java @@ -23,11 +23,11 @@ import com.sonar.sslr.api.AstNodeType; import java.util.Collections; import java.util.Set; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.semantic.Symbol; -public abstract class AbstractCallExpressionCheck extends PythonCheck { +public abstract class AbstractCallExpressionCheck extends PythonCheckAstNode { protected abstract Set functionsToCheck(); diff --git a/python-checks/src/main/java/org/sonar/python/checks/AbstractNameCheck.java b/python-checks/src/main/java/org/sonar/python/checks/AbstractNameCheck.java index a71bec2424..01ac328619 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/AbstractNameCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/AbstractNameCheck.java @@ -20,9 +20,9 @@ package org.sonar.python.checks; import java.util.regex.Pattern; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; -public abstract class AbstractNameCheck extends PythonCheck { +public abstract class AbstractNameCheck extends PythonCheckAstNode { private Pattern pattern = null; 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 6abae37ed1..4b4005d00f 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 @@ -23,11 +23,11 @@ import com.sonar.sslr.api.AstNodeType; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = AfterJumpStatementCheck.CHECK_KEY) -public class AfterJumpStatementCheck extends PythonCheck { +public class AfterJumpStatementCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1763"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/BackslashInStringCheck.java b/python-checks/src/main/java/org/sonar/python/checks/BackslashInStringCheck.java index 2443ca458b..a204ba5a4f 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 @@ -24,11 +24,11 @@ import java.util.Collections; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonTokenType; @Rule(key = "S1717") -public class BackslashInStringCheck extends PythonCheck { +public class BackslashInStringCheck extends PythonCheckAstNode { private static final String MESSAGE = "Remove this \"\\\", add another \"\\\" to escape it, or make this a raw string."; private static final String VALID_ESCAPED_CHARACTERS = "abfnrtvxnNrtuU\\'\"0123456789\n\r"; 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 2a1a918a71..dec42aab98 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 @@ -24,12 +24,12 @@ import java.util.Collections; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonPunctuator; @Rule(key = BackticksUsageCheck.CHECK_KEY) -public class BackticksUsageCheck extends PythonCheck { +public class BackticksUsageCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "BackticksUsage"; @Override 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 398f7da19d..91f86e5514 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 @@ -23,11 +23,11 @@ import com.sonar.sslr.api.AstNodeType; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = BreakContinueOutsideLoopCheck.CHECK_KEY) -public class BreakContinueOutsideLoopCheck extends PythonCheck { +public class BreakContinueOutsideLoopCheck extends PythonCheckAstNode { private static final String MESSAGE = "Remove this \"%s\" statement"; public static final String CHECK_KEY = "S1716"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/CheckList.java b/python-checks/src/main/java/org/sonar/python/checks/CheckList.java index df5a4670a5..9a6b659921 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/CheckList.java +++ b/python-checks/src/main/java/org/sonar/python/checks/CheckList.java @@ -19,7 +19,9 @@ */ package org.sonar.python.checks; -import org.sonar.python.PythonCheck; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import org.sonar.python.checks.hotspots.CommandLineArgsCheck; import org.sonar.python.checks.hotspots.DebugModeCheck; import org.sonar.python.checks.hotspots.DynamicCodeExecutionCheck; @@ -38,7 +40,7 @@ private CheckList() { } public static Iterable getChecks() { - return PythonCheck.immutableSet( + return Collections.unmodifiableSet(new HashSet<>(Arrays.asList( AfterJumpStatementCheck.class, BackslashInStringCheck.class, BackticksUsageCheck.class, @@ -102,7 +104,7 @@ public static Iterable getChecks() { UselessParenthesisAfterKeywordCheck.class, UselessParenthesisCheck.class, XPathCheck.class - ); + ))); } } 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 ce0c3bc150..e3eaa9a64c 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 @@ -25,12 +25,12 @@ import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.metrics.ComplexityVisitor; @Rule(key = "ClassComplexity") -public class ClassComplexityCheck extends PythonCheck { +public class ClassComplexityCheck extends PythonCheckAstNode { private static final int DEFAULT_MAXIMUM_CLASS_COMPLEXITY_THRESHOLD = 200; private static final String MESSAGE = "Class has a complexity of %s which is greater than %s authorized."; 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 31163e8ddf..255085b5cc 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 @@ -27,12 +27,12 @@ import org.sonar.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.python.IssueLocation; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.metrics.CognitiveComplexityVisitor; @Rule(key = CognitiveComplexityFunctionCheck.CHECK_KEY) -public class CognitiveComplexityFunctionCheck extends PythonCheck { +public class CognitiveComplexityFunctionCheck extends PythonCheckAstNode { 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"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/CollapsibleIfStatementsCheck.java b/python-checks/src/main/java/org/sonar/python/checks/CollapsibleIfStatementsCheck.java index 5dcc4fbca6..8896631cc7 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/CollapsibleIfStatementsCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/CollapsibleIfStatementsCheck.java @@ -19,54 +19,50 @@ */ package org.sonar.python.checks; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; -import java.util.Collections; +import java.util.HashSet; 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; +import org.sonar.python.PythonCheckTree; +import org.sonar.python.PythonVisitorContext; +import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.Tree; @Rule(key = CollapsibleIfStatementsCheck.CHECK_KEY) -public class CollapsibleIfStatementsCheck extends PythonCheck { +public class CollapsibleIfStatementsCheck extends PythonCheckTree { public static final String CHECK_KEY = "S1066"; private static final String MESSAGE = "Merge this if statement with the enclosing one."; + private Set ignored = new HashSet<>(); + @Override - public Set subscribedKinds() { - return Collections.singleton(PythonGrammar.IF_STMT); + public void scanFile(PythonVisitorContext visitorContext) { + ignored.clear(); + super.scanFile(visitorContext); } @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"); + public void visitIfStatement(PyIfStatementTree ifStatement) { + List statements = ifStatement.body(); + if (!ifStatement.elifBranches().isEmpty()) { + if (ifStatement.elseBranch() == null) { + ignored.addAll(ifStatement.elifBranches().subList(0, ifStatement.elifBranches().size() - 1)); + } else { + ignored.addAll(ifStatement.elifBranches()); + } } - } - - private static boolean hasElseOrElif(AstNode ifNode) { - return ifNode.hasDirectChildren(PythonKeyword.ELIF) || ifNode.hasDirectChildren(PythonKeyword.ELSE); - } - - 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); + if (!ignored.contains(ifStatement) + && ifStatement.elseBranch() == null + && ifStatement.elifBranches().isEmpty() + && statements.size() == 1 + && statements.get(0).is(Tree.Kind.IF_STATEMENT)) { + PyIfStatementTree singleIfChild = (PyIfStatementTree) statements.get(0); + if (singleIfChild.isElif() || singleIfChild.elseBranch() != null || !singleIfChild.elifBranches().isEmpty()) { + return; } + addIssue(singleIfChild.keyword(), MESSAGE).secondary(ifStatement.astNode(), "enclosing"); } - return null; + super.visitIfStatement(ifStatement); } } 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 88b5eadb11..9fb04028d5 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 @@ -24,10 +24,10 @@ import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = CommentRegularExpressionCheck.CHECK_KEY) -public class CommentRegularExpressionCheck extends PythonCheck { +public class CommentRegularExpressionCheck extends PythonCheckAstNode { 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"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/CommentedCodeCheck.java b/python-checks/src/main/java/org/sonar/python/checks/CommentedCodeCheck.java index 3cc74c2ddc..3aec506e97 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/CommentedCodeCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/CommentedCodeCheck.java @@ -32,14 +32,14 @@ import java.util.Set; import javax.annotation.Nullable; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.PythonConfiguration; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonTokenType; import org.sonar.python.parser.PythonParser; @Rule(key = CommentedCodeCheck.CHECK_KEY) -public class CommentedCodeCheck extends PythonCheck { +public class CommentedCodeCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S125"; public static final String MESSAGE = "Remove this commented out code."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/DuplicatedMethodFieldNamesCheck.java b/python-checks/src/main/java/org/sonar/python/checks/DuplicatedMethodFieldNamesCheck.java index 189f954148..348375a494 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/DuplicatedMethodFieldNamesCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/DuplicatedMethodFieldNamesCheck.java @@ -29,12 +29,12 @@ import java.util.List; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.sslr.ast.AstSelect; @Rule(key = DuplicatedMethodFieldNamesCheck.CHECK_KEY) -public class DuplicatedMethodFieldNamesCheck extends PythonCheck { +public class DuplicatedMethodFieldNamesCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1845"; private static final String MESSAGE = "Rename %s \"%s\" to prevent any misunderstanding/clash with %s \"%s\" defined on line %s"; 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 084c3eb0b7..1f36f39387 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 @@ -27,12 +27,12 @@ import java.util.Set; import java.util.function.Predicate; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.sslr.ast.AstSelect; @Rule(key = EmptyNestedBlockCheck.CHECK_KEY) -public class EmptyNestedBlockCheck extends PythonCheck { +public class EmptyNestedBlockCheck extends PythonCheckAstNode { 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."; 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 f45452de54..0976acf394 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 @@ -24,11 +24,11 @@ import java.util.Collections; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = ExecStatementUsageCheck.CHECK_KEY) -public class ExecStatementUsageCheck extends PythonCheck { +public class ExecStatementUsageCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "ExecStatementUsage"; @Override public Set subscribedKinds() { 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 9df49c4559..85bbc96950 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 @@ -26,12 +26,12 @@ import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.IssueLocation; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonPunctuator; @Rule(key = ExitHasBadArgumentsCheck.CHECK_KEY) -public class ExitHasBadArgumentsCheck extends PythonCheck { +public class ExitHasBadArgumentsCheck extends PythonCheckAstNode { public static final String MESSAGE_ADD = "Add the missing argument."; public static final String MESSAGE_REMOVE = "Remove the unnecessary argument."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/FieldDuplicatesClassNameCheck.java b/python-checks/src/main/java/org/sonar/python/checks/FieldDuplicatesClassNameCheck.java index b7fbe0b8e2..fc2b94cd87 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/FieldDuplicatesClassNameCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/FieldDuplicatesClassNameCheck.java @@ -26,11 +26,11 @@ import java.util.List; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = FieldDuplicatesClassNameCheck.CHECK_KEY) -public class FieldDuplicatesClassNameCheck extends PythonCheck { +public class FieldDuplicatesClassNameCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1700"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/FieldNameCheck.java b/python-checks/src/main/java/org/sonar/python/checks/FieldNameCheck.java index d36bfa9f7f..15f3d0089b 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/FieldNameCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/FieldNameCheck.java @@ -28,11 +28,11 @@ import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = FieldNameCheck.CHECK_KEY) -public class FieldNameCheck extends PythonCheck { +public class FieldNameCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S116"; 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 0f43ce88fc..d82006247d 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 @@ -23,11 +23,11 @@ import java.text.MessageFormat; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.metrics.ComplexityVisitor; @Rule(key = "FileComplexity") -public class FileComplexityCheck extends PythonCheck { +public class FileComplexityCheck extends PythonCheckAstNode { private static final int DEFAULT_MAXIMUM_FILE_COMPLEXITY_THRESHOLD = 200; @RuleProperty( 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 75a6fa51c3..0023eb483b 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 @@ -24,10 +24,10 @@ import com.sonar.sslr.api.Trivia; import java.util.regex.Pattern; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = FixmeCommentCheck.CHECK_KEY) -public class FixmeCommentCheck extends PythonCheck { +public class FixmeCommentCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1134"; 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 071eb5f2aa..0b786f1a05 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 @@ -25,12 +25,12 @@ import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.metrics.ComplexityVisitor; @Rule(key = "FunctionComplexity") -public class FunctionComplexityCheck extends PythonCheck { +public class FunctionComplexityCheck extends PythonCheckAstNode { private static final int DEFAULT_MAXIMUM_FUNCTION_COMPLEXITY_THRESHOLD = 15; private static final String MESSAGE = "Function has a complexity of %s which is greater than %s authorized."; 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 f6935b9351..3744b7ed4b 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 @@ -29,11 +29,11 @@ import java.util.regex.Pattern; import javax.annotation.Nullable; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonTokenType; @Rule(key = HardcodedIPCheck.CHECK_KEY) -public class HardcodedIPCheck extends PythonCheck { +public class HardcodedIPCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1313"; private static final String IPV4_ALONE = "(?(?:\\d{1,3}\\.){3}\\d{1,3})"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/IdenticalExpressionOnBinaryOperatorCheck.java b/python-checks/src/main/java/org/sonar/python/checks/IdenticalExpressionOnBinaryOperatorCheck.java index 1fb2527248..52c53314ed 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/IdenticalExpressionOnBinaryOperatorCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/IdenticalExpressionOnBinaryOperatorCheck.java @@ -26,11 +26,11 @@ import java.util.List; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = "S1764") -public class IdenticalExpressionOnBinaryOperatorCheck extends PythonCheck { +public class IdenticalExpressionOnBinaryOperatorCheck extends PythonCheckAstNode { private static final List EXCLUDED_OPERATOR_TYPES = Collections.unmodifiableList(Arrays.asList( "*", 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 b2cc109e34..a1cd140c26 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 @@ -24,11 +24,11 @@ import java.util.Collections; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonPunctuator; @Rule(key = InequalityUsageCheck.CHECK_KEY) -public class InequalityUsageCheck extends PythonCheck { +public class InequalityUsageCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "InequalityUsage"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/InitReturnsValueCheck.java b/python-checks/src/main/java/org/sonar/python/checks/InitReturnsValueCheck.java index 4d6c7c9c12..d6cfb5ff27 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/InitReturnsValueCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/InitReturnsValueCheck.java @@ -25,12 +25,12 @@ import java.util.List; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; @Rule(key = InitReturnsValueCheck.CHECK_KEY) -public class InitReturnsValueCheck extends PythonCheck { +public class InitReturnsValueCheck extends PythonCheckAstNode { public static final String MESSAGE_RETURN = "Remove this return value."; public static final String MESSAGE_YIELD = "Remove this yield statement."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/LineLengthCheck.java b/python-checks/src/main/java/org/sonar/python/checks/LineLengthCheck.java index d572af7cb1..51414d3738 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/LineLengthCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/LineLengthCheck.java @@ -24,10 +24,10 @@ import java.text.MessageFormat; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = LineLengthCheck.CHECK_KEY) -public class LineLengthCheck extends PythonCheck { +public class LineLengthCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "LineLength"; private static final int DEFAULT_MAXIMUM_LINE_LENGTH = 120; diff --git a/python-checks/src/main/java/org/sonar/python/checks/LocalVariableAndParameterNameConventionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/LocalVariableAndParameterNameConventionCheck.java index e1b4a4e845..5d35af7487 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/LocalVariableAndParameterNameConventionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/LocalVariableAndParameterNameConventionCheck.java @@ -30,11 +30,11 @@ import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = LocalVariableAndParameterNameConventionCheck.CHECK_KEY) -public class LocalVariableAndParameterNameConventionCheck extends PythonCheck { +public class LocalVariableAndParameterNameConventionCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S117"; 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 c55becbdbc..e5c7e86f6f 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 @@ -24,11 +24,11 @@ import java.util.Collections; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonTokenType; @Rule(key = LongIntegerWithLowercaseSuffixUsageCheck.CHECK_KEY) -public class LongIntegerWithLowercaseSuffixUsageCheck extends PythonCheck { +public class LongIntegerWithLowercaseSuffixUsageCheck extends PythonCheckAstNode { 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\"."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/MethodShouldBeStaticCheck.java b/python-checks/src/main/java/org/sonar/python/checks/MethodShouldBeStaticCheck.java index 576facd8a0..e881bccee1 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/MethodShouldBeStaticCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/MethodShouldBeStaticCheck.java @@ -26,12 +26,12 @@ import java.util.Objects; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonTokenType; @Rule(key = MethodShouldBeStaticCheck.CHECK_KEY) -public class MethodShouldBeStaticCheck extends PythonCheck { +public class MethodShouldBeStaticCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S2325"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java b/python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java index 219d8312f4..1c1edd85e2 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java @@ -26,11 +26,11 @@ import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.python.DocstringExtractor; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = MissingDocstringCheck.CHECK_KEY) -public class MissingDocstringCheck extends PythonCheck { +public class MissingDocstringCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1720"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/MissingNewlineAtEndOfFileCheck.java b/python-checks/src/main/java/org/sonar/python/checks/MissingNewlineAtEndOfFileCheck.java index ceef1818bd..259fa1e9c4 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/MissingNewlineAtEndOfFileCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/MissingNewlineAtEndOfFileCheck.java @@ -22,10 +22,10 @@ import com.sonar.sslr.api.AstNode; import javax.annotation.Nullable; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = MissingNewlineAtEndOfFileCheck.CHECK_KEY) -public class MissingNewlineAtEndOfFileCheck extends PythonCheck { +public class MissingNewlineAtEndOfFileCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S113"; public static final String MESSAGE = "Add a new line at the end of this file \"%s\"."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/ModuleNameCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ModuleNameCheck.java index 279b4666b9..cc48453bb1 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ModuleNameCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ModuleNameCheck.java @@ -24,10 +24,10 @@ import javax.annotation.Nullable; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = ModuleNameCheck.CHECK_KEY) -public class ModuleNameCheck extends PythonCheck { +public class ModuleNameCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1578"; private static final String DEFAULT = "(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/NeedlessPassCheck.java b/python-checks/src/main/java/org/sonar/python/checks/NeedlessPassCheck.java index a6f1c97bea..403da3ba23 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/NeedlessPassCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/NeedlessPassCheck.java @@ -25,13 +25,13 @@ import java.util.List; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonTokenType; import org.sonar.sslr.ast.AstSelect; @Rule(key = NeedlessPassCheck.CHECK_KEY) -public class NeedlessPassCheck extends PythonCheck { +public class NeedlessPassCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S2772"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/NestedControlFlowDepthCheck.java b/python-checks/src/main/java/org/sonar/python/checks/NestedControlFlowDepthCheck.java index 6954f08b3c..0162570cf7 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/NestedControlFlowDepthCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/NestedControlFlowDepthCheck.java @@ -27,11 +27,11 @@ import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = NestedControlFlowDepthCheck.CHECK_KEY) -public class NestedControlFlowDepthCheck extends PythonCheck { +public class NestedControlFlowDepthCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S134"; private static final int DEFAULT_MAX = 4; diff --git a/python-checks/src/main/java/org/sonar/python/checks/NewStyleClassCheck.java b/python-checks/src/main/java/org/sonar/python/checks/NewStyleClassCheck.java index bd7db83a89..e1f7fa2b3a 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/NewStyleClassCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/NewStyleClassCheck.java @@ -23,11 +23,11 @@ import com.sonar.sslr.api.AstNodeType; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = NewStyleClassCheck.CHECK_KEY) -public class NewStyleClassCheck extends PythonCheck { +public class NewStyleClassCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1722"; private static final String MESSAGE = "Add inheritance from \"object\" or some other new-style class."; 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 02b3c51266..a2f0897e5c 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 @@ -26,10 +26,10 @@ import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = NoPersonReferenceInTodoCheck.CHECK_KEY) -public class NoPersonReferenceInTodoCheck extends PythonCheck { +public class NoPersonReferenceInTodoCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1707"; public static final String MESSAGE = "Add a citation of the person who can best explain this comment."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/OneStatementPerLineCheck.java b/python-checks/src/main/java/org/sonar/python/checks/OneStatementPerLineCheck.java index 9a5085cdfb..64bd8b50f0 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/OneStatementPerLineCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/OneStatementPerLineCheck.java @@ -25,14 +25,14 @@ import java.util.Map; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; /** * Note that implementation differs from AbstractOneStatementPerLineCheck due to Python specifics */ @Rule(key = OneStatementPerLineCheck.CHECK_KEY) -public class OneStatementPerLineCheck extends PythonCheck { +public class OneStatementPerLineCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "OneStatementPerLine"; private final Map statementsPerLine = new HashMap<>(); diff --git a/python-checks/src/main/java/org/sonar/python/checks/ParsingErrorCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ParsingErrorCheck.java index f0b53f98f1..87085bebc4 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ParsingErrorCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ParsingErrorCheck.java @@ -21,11 +21,11 @@ import com.sonar.sslr.api.RecognitionException; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.PythonVisitorContext; @Rule(key = ParsingErrorCheck.CHECK_KEY) -public class ParsingErrorCheck extends PythonCheck { +public class ParsingErrorCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "ParsingError"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/PreIncrementDecrementCheck.java b/python-checks/src/main/java/org/sonar/python/checks/PreIncrementDecrementCheck.java index 083e03d538..17f67dd351 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/PreIncrementDecrementCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/PreIncrementDecrementCheck.java @@ -25,12 +25,12 @@ import java.util.List; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonPunctuator; @Rule(key = PreIncrementDecrementCheck.CHECK_KEY) -public class PreIncrementDecrementCheck extends PythonCheck { +public class PreIncrementDecrementCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "PreIncrementDecrement"; private static final String MESSAGE = "This statement doesn't produce the expected result, replace use of non-existent pre-%srement operator"; 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 ec555eadc0..e463c7a316 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 @@ -24,11 +24,11 @@ import java.util.Collections; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = PrintStatementUsageCheck.CHECK_KEY) -public class PrintStatementUsageCheck extends PythonCheck { +public class PrintStatementUsageCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "PrintStatementUsage"; @Override diff --git a/python-checks/src/main/java/org/sonar/python/checks/ReturnAndYieldInOneFunctionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ReturnAndYieldInOneFunctionCheck.java index a9717d9022..51dac61d92 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ReturnAndYieldInOneFunctionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ReturnAndYieldInOneFunctionCheck.java @@ -25,11 +25,11 @@ import java.util.List; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = ReturnAndYieldInOneFunctionCheck.CHECK_KEY) -public class ReturnAndYieldInOneFunctionCheck extends PythonCheck { +public class ReturnAndYieldInOneFunctionCheck extends PythonCheckAstNode { public static final String MESSAGE = "Use only \"return\" or only \"yield\", not both."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/ReturnYieldOutsideFunctionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ReturnYieldOutsideFunctionCheck.java index ef95ea658a..de436928f5 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ReturnYieldOutsideFunctionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ReturnYieldOutsideFunctionCheck.java @@ -23,11 +23,11 @@ import com.sonar.sslr.api.AstNodeType; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = ReturnYieldOutsideFunctionCheck.CHECK_KEY) -public class ReturnYieldOutsideFunctionCheck extends PythonCheck { +public class ReturnYieldOutsideFunctionCheck extends PythonCheckAstNode { public static final String MESSAGE = "Remove this use of \"%s\"."; public static final String CHECK_KEY = "S2711"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/SameBranchCheck.java b/python-checks/src/main/java/org/sonar/python/checks/SameBranchCheck.java index dc40b8e098..8450e56ff6 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/SameBranchCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/SameBranchCheck.java @@ -28,7 +28,7 @@ import javax.annotation.Nullable; import org.sonar.check.Rule; import org.sonar.python.IssueLocation; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; import org.sonar.python.api.PythonPunctuator; @@ -36,7 +36,7 @@ import org.sonar.sslr.ast.AstSelect; @Rule(key = SameBranchCheck.CHECK_KEY) -public class SameBranchCheck extends PythonCheck { +public class SameBranchCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1871"; public static final String MESSAGE = "Either merge this branch with the identical one on line \"%s\" or change one of the implementations."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/SameConditionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/SameConditionCheck.java index ba8257c1f8..677b7d3007 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/SameConditionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/SameConditionCheck.java @@ -27,13 +27,13 @@ import java.util.Set; import javax.annotation.Nullable; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; import org.sonar.sslr.ast.AstSelect; @Rule(key = SameConditionCheck.CHECK_KEY) -public class SameConditionCheck extends PythonCheck { +public class SameConditionCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1862"; private static final String MESSAGE = "This branch duplicates the one on line %s."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/SelfAssignmentCheck.java b/python-checks/src/main/java/org/sonar/python/checks/SelfAssignmentCheck.java index cfedd11bac..45efb0b899 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/SelfAssignmentCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/SelfAssignmentCheck.java @@ -25,13 +25,13 @@ import java.util.Set; import org.sonar.check.Rule; import org.sonar.python.PythonBuiltinFunctions; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; import org.sonar.python.api.PythonPunctuator; @Rule(key = SelfAssignmentCheck.CHECK_KEY) -public class SelfAssignmentCheck extends PythonCheck { +public class SelfAssignmentCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1656"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/TooManyLinesInFileCheck.java b/python-checks/src/main/java/org/sonar/python/checks/TooManyLinesInFileCheck.java index 02fb5f9072..d10a643e98 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/TooManyLinesInFileCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/TooManyLinesInFileCheck.java @@ -27,10 +27,10 @@ import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = TooManyLinesInFileCheck.CHECK_KEY) -public class TooManyLinesInFileCheck extends PythonCheck { +public class TooManyLinesInFileCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S104"; private static final int DEFAULT = 1000; diff --git a/python-checks/src/main/java/org/sonar/python/checks/TooManyParametersCheck.java b/python-checks/src/main/java/org/sonar/python/checks/TooManyParametersCheck.java index a5b2af3253..055871b5d2 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/TooManyParametersCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/TooManyParametersCheck.java @@ -24,11 +24,11 @@ import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = TooManyParametersCheck.CHECK_KEY) -public class TooManyParametersCheck extends PythonCheck { +public class TooManyParametersCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S107"; private static final String MESSAGE = "%s has %s parameters, which is greater than the %s authorized."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/TooManyReturnsCheck.java b/python-checks/src/main/java/org/sonar/python/checks/TooManyReturnsCheck.java index 0a4f99a9a7..1fb9cadd0b 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/TooManyReturnsCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/TooManyReturnsCheck.java @@ -27,12 +27,12 @@ import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.sslr.ast.AstSelect; @Rule(key = TooManyReturnsCheck.CHECK_KEY) -public class TooManyReturnsCheck extends PythonCheck { +public class TooManyReturnsCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1142"; private static final int DEFAULT_MAX = 3; diff --git a/python-checks/src/main/java/org/sonar/python/checks/TrailingCommentCheck.java b/python-checks/src/main/java/org/sonar/python/checks/TrailingCommentCheck.java index a3607ee9b9..6d624ef69c 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/TrailingCommentCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/TrailingCommentCheck.java @@ -25,10 +25,10 @@ import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = TrailingCommentCheck.CHECK_KEY) -public class TrailingCommentCheck extends PythonCheck { +public class TrailingCommentCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S139"; private static final String DEFAULT_LEGAL_COMMENT_PATTERN = "^#\\s*+[^\\s]++$"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/TrailingWhitespaceCheck.java b/python-checks/src/main/java/org/sonar/python/checks/TrailingWhitespaceCheck.java index 549df311b8..b0af7c58f4 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/TrailingWhitespaceCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/TrailingWhitespaceCheck.java @@ -23,10 +23,10 @@ import java.util.regex.Pattern; import javax.annotation.Nullable; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = TrailingWhitespaceCheck.CHECK_KEY) -public class TrailingWhitespaceCheck extends PythonCheck { +public class TrailingWhitespaceCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1131"; public static final String MESSAGE = "Remove the useless trailing whitespaces at the end of this line."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/UnusedLocalVariableCheck.java b/python-checks/src/main/java/org/sonar/python/checks/UnusedLocalVariableCheck.java index e7990ed9a1..f8bba27464 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/UnusedLocalVariableCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/UnusedLocalVariableCheck.java @@ -28,12 +28,12 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.semantic.Symbol; @Rule(key = "S1481") -public class UnusedLocalVariableCheck extends PythonCheck { +public class UnusedLocalVariableCheck extends PythonCheckAstNode { private static final Pattern IDENTIFIER_SEPARATOR = Pattern.compile("[^a-zA-Z0-9_]+"); diff --git a/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisAfterKeywordCheck.java b/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisAfterKeywordCheck.java index 775d2dcb2c..1fb535fe80 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisAfterKeywordCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisAfterKeywordCheck.java @@ -28,12 +28,12 @@ import java.util.Set; import javax.annotation.Nullable; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonPunctuator; @Rule(key = UselessParenthesisAfterKeywordCheck.CHECK_KEY) -public class UselessParenthesisAfterKeywordCheck extends PythonCheck { +public class UselessParenthesisAfterKeywordCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1721"; private static final Map KEYWORDS_FOLLOWED_BY_TEST = initializeKeywordsFollowedByTest(); diff --git a/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisCheck.java b/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisCheck.java index bee3545b10..f83b8e6765 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisCheck.java @@ -25,13 +25,13 @@ import java.util.List; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; import org.sonar.python.api.PythonPunctuator; @Rule(key = UselessParenthesisCheck.CHECK_KEY) -public class UselessParenthesisCheck extends PythonCheck { +public class UselessParenthesisCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1110"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/XPathCheck.java b/python-checks/src/main/java/org/sonar/python/checks/XPathCheck.java index dbc3118f77..7fe4fcb7d6 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/XPathCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/XPathCheck.java @@ -24,10 +24,10 @@ import javax.annotation.CheckForNull; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; @Rule(key = XPathCheck.CHECK_KEY) -public class XPathCheck extends PythonCheck { +public class XPathCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "XPath"; private static final String DEFAULT_XPATH_QUERY = ""; private static final String DEFAULT_MESSAGE = "The XPath expression matches this piece of code"; diff --git a/python-checks/src/main/java/org/sonar/python/checks/hotspots/DebugModeCheck.java b/python-checks/src/main/java/org/sonar/python/checks/hotspots/DebugModeCheck.java index 4ac70849c3..37b8799b73 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/hotspots/DebugModeCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/hotspots/DebugModeCheck.java @@ -23,13 +23,13 @@ import com.sonar.sslr.api.AstNodeType; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonPunctuator; import org.sonar.python.semantic.Symbol; @Rule(key = DebugModeCheck.CHECK_KEY) -public class DebugModeCheck extends PythonCheck { +public class DebugModeCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S4507"; private static final String MESSAGE = "Make sure this debug feature is deactivated before delivering the code in production."; private static final Set debugProperties = immutableSet("DEBUG", "DEBUG_PROPAGATE_EXCEPTIONS"); diff --git a/python-checks/src/main/java/org/sonar/python/checks/hotspots/DynamicCodeExecutionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/hotspots/DynamicCodeExecutionCheck.java index 94cfb7e442..1a61a4a131 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/hotspots/DynamicCodeExecutionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/hotspots/DynamicCodeExecutionCheck.java @@ -23,11 +23,11 @@ import com.sonar.sslr.api.AstNodeType; import java.util.Set; import org.sonar.check.Rule; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; @Rule(key = DynamicCodeExecutionCheck.CHECK_KEY) -public class DynamicCodeExecutionCheck extends PythonCheck { +public class DynamicCodeExecutionCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S1523"; private static final String MESSAGE = "Make sure that this dynamic injection or execution of code is safe."; diff --git a/python-checks/src/main/java/org/sonar/python/checks/hotspots/RegexCheck.java b/python-checks/src/main/java/org/sonar/python/checks/hotspots/RegexCheck.java index a1d9da891a..5445216355 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/hotspots/RegexCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/hotspots/RegexCheck.java @@ -25,13 +25,13 @@ import javax.annotation.CheckForNull; import org.sonar.check.Rule; import org.sonar.python.IssueLocation; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonPunctuator; import org.sonar.python.semantic.Symbol; @Rule(key = RegexCheck.CHECK_KEY) -public class RegexCheck extends PythonCheck { +public class RegexCheck extends PythonCheckAstNode { public static final String CHECK_KEY = "S4784"; private static final String MESSAGE = "Make sure that using a regular expression is safe here."; private static final int REGEX_ARGUMENT = 0; diff --git a/python-checks/src/test/java/org/sonar/python/checks/CheckUtilsTest.java b/python-checks/src/test/java/org/sonar/python/checks/CheckUtilsTest.java index c555b60490..b327483196 100644 --- a/python-checks/src/test/java/org/sonar/python/checks/CheckUtilsTest.java +++ b/python-checks/src/test/java/org/sonar/python/checks/CheckUtilsTest.java @@ -30,7 +30,7 @@ import java.util.function.Predicate; import java.util.function.UnaryOperator; import org.junit.Test; -import org.sonar.python.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.PythonConfiguration; import org.sonar.python.api.PythonGrammar; import org.sonar.python.checks.utils.PythonCheckVerifier; @@ -57,7 +57,7 @@ public void equals_node() { PythonCheckVerifier.verify("src/test/resources/checks/utils/equalsNode.py", new EqualsNodeCheck()); } - private static class EqualsNodeCheck extends PythonCheck { + private static class EqualsNodeCheck extends PythonCheckAstNode { @Override public Set subscribedKinds() { return ImmutableSet.copyOf(PythonGrammar.values()); @@ -81,7 +81,7 @@ public void is_method_definition() { PythonCheckVerifier.verify("src/test/resources/checks/utils/isMethodDefinition.py", new IsMethodDefinitionCheck()); } - private static class IsMethodDefinitionCheck extends PythonCheck { + private static class IsMethodDefinitionCheck extends PythonCheckAstNode { @Override public Set subscribedKinds() { return ImmutableSet.of( @@ -106,7 +106,7 @@ public void class_has_inheritance() { PythonCheckVerifier.verify("src/test/resources/checks/utils/classHasInheritance.py", new ClassHasInheritanceCheck()); } - private static class ClassHasInheritanceCheck extends PythonCheck { + private static class ClassHasInheritanceCheck extends PythonCheckAstNode { @Override public Set subscribedKinds() { return ImmutableSet.of(PythonGrammar.CLASSDEF); 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 3a00f0eba1..380b74766e 100644 --- a/python-squid/src/main/java/org/sonar/python/IssueLocation.java +++ b/python-squid/src/main/java/org/sonar/python/IssueLocation.java @@ -52,6 +52,10 @@ public static IssueLocation preciseLocation(AstNode startNode, @Nullable String return new PreciseIssueLocation(startNode, message); } + public static IssueLocation preciseLocation(Token token, @Nullable String message) { + return new PreciseIssueLocation(token, message); + } + @CheckForNull public String message() { return message; @@ -82,6 +86,12 @@ public PreciseIssueLocation(AstNode startNode, AstNode endNode, String message) this.lastTokenLocation = new TokenLocation(endNode.getLastToken()); } + public PreciseIssueLocation(Token token, String message) { + super(message); + this.firstToken = token; + this.lastTokenLocation = new TokenLocation(token); + } + @Override public int startLine() { return firstToken.getLine(); 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 4423ed33da..98d59cf5d2 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonCheck.java +++ b/python-squid/src/main/java/org/sonar/python/PythonCheck.java @@ -20,53 +20,23 @@ package org.sonar.python; import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.Token; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; import javax.annotation.Nullable; -public abstract class PythonCheck extends PythonVisitor { +public interface PythonCheck { - protected final PreciseIssue addIssue(AstNode node, @Nullable String message) { - PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.preciseLocation(node, message)); - getContext().addIssue(newIssue); - return newIssue; - } - - protected final PreciseIssue addIssue(IssueLocation primaryLocation) { - PreciseIssue newIssue = new PreciseIssue(this, primaryLocation); - getContext().addIssue(newIssue); - return newIssue; - } - - protected final PreciseIssue addLineIssue(String message, int lineNumber) { - PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.atLineLevel(message, lineNumber)); - getContext().addIssue(newIssue); - return newIssue; - } - protected final PreciseIssue addFileIssue(String message) { - PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.atFileLevel(message)); - getContext().addIssue(newIssue); - return newIssue; - } - - protected final PreciseIssue addIssue(Token token, String message) { - return addIssue(new AstNode(token), message); - } + void scanFile(PythonVisitorContext visitorContext); - public static class PreciseIssue { + class PreciseIssue { private final PythonCheck check; private final IssueLocation primaryLocation; private Integer cost; private final List secondaryLocations; - private PreciseIssue(PythonCheck check, IssueLocation primaryLocation) { + protected PreciseIssue(PythonCheck check, IssueLocation primaryLocation) { this.check = check; this.primaryLocation = primaryLocation; this.secondaryLocations = new ArrayList<>(); @@ -104,8 +74,4 @@ public PythonCheck check() { return check; } } - - public static Set immutableSet(T... el) { - return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(el))); - } } diff --git a/python-squid/src/main/java/org/sonar/python/PythonCheckAstNode.java b/python-squid/src/main/java/org/sonar/python/PythonCheckAstNode.java new file mode 100644 index 0000000000..521cf04547 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/PythonCheckAstNode.java @@ -0,0 +1,63 @@ +/* + * 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.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nullable; + +public abstract class PythonCheckAstNode extends PythonVisitor implements PythonCheck { + + protected final PreciseIssue addIssue(AstNode node, @Nullable String message) { + PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.preciseLocation(node, message)); + getContext().addIssue(newIssue); + return newIssue; + } + + protected final PreciseIssue addIssue(IssueLocation primaryLocation) { + PreciseIssue newIssue = new PreciseIssue(this, primaryLocation); + getContext().addIssue(newIssue); + return newIssue; + } + + protected final PreciseIssue addLineIssue(String message, int lineNumber) { + PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.atLineLevel(message, lineNumber)); + getContext().addIssue(newIssue); + return newIssue; + } + + protected final PreciseIssue addFileIssue(String message) { + PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.atFileLevel(message)); + getContext().addIssue(newIssue); + return newIssue; + } + + protected final PreciseIssue addIssue(Token token, String message) { + return addIssue(new AstNode(token), message); + } + + public static Set immutableSet(T... el) { + return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(el))); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/PythonCheckTree.java b/python-squid/src/main/java/org/sonar/python/PythonCheckTree.java new file mode 100644 index 0000000000..227dfcf13d --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/PythonCheckTree.java @@ -0,0 +1,60 @@ +/* + * 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.sonar.sslr.api.Token; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nullable; +import org.sonar.python.api.tree.Tree; +import org.sonar.python.tree.BaseTreeVisitor; + +public abstract class PythonCheckTree extends BaseTreeVisitor implements PythonCheck { + + private PythonVisitorContext context; + + private PythonVisitorContext getContext() { + return context; + } + + protected final PreciseIssue addIssue(Token token, @Nullable String message) { + PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.preciseLocation(token, message)); + getContext().addIssue(newIssue); + return newIssue; + } + + protected final PreciseIssue addIssue(Tree node, @Nullable String message) { + PreciseIssue newIssue = new PreciseIssue(this, IssueLocation.preciseLocation(node.astNode(), message)); + getContext().addIssue(newIssue); + return newIssue; + } + + public static Set immutableSet(T... el) { + return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(el))); + } + + @Override + public void scanFile(PythonVisitorContext visitorContext) { + this.context = visitorContext; + scan(context.rootTree()); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/PythonVisitor.java b/python-squid/src/main/java/org/sonar/python/PythonVisitor.java index fccd2b9868..4fb2b4ae95 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/PythonVisitor.java @@ -60,7 +60,7 @@ public PythonVisitorContext getContext() { public void scanFile(PythonVisitorContext context) { this.context = context; - AstNode tree = context.rootTree(); + AstNode tree = (AstNode) context.rootTree(); if (tree != null) { visitFile(tree); scanNode(tree, subscribedKinds()); 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 1dbcc74110..199e700124 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java +++ b/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java @@ -19,15 +19,14 @@ */ package org.sonar.python; -import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.RecognitionException; import java.util.ArrayList; import java.util.List; import org.sonar.python.PythonCheck.PreciseIssue; import org.sonar.python.api.tree.PyFileInputTree; -import org.sonar.python.tree.PyFileInputTreeImpl; import org.sonar.python.semantic.SymbolTable; import org.sonar.python.semantic.SymbolTableBuilderVisitor; +import org.sonar.python.tree.PyFileInputTreeImpl; public class PythonVisitorContext { @@ -54,7 +53,7 @@ private PythonVisitorContext(PyFileInputTree rootTree, PythonFile pythonFile, Re this.parsingException = parsingException; } - public AstNode rootTree() { + public PyFileInputTree rootTree() { return rootTree; } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java new file mode 100644 index 0000000000..01fcdca3ef --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -0,0 +1,41 @@ +/* + * 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.api.tree; + +public interface PyTreeVisitor { + + void visitFileInput(PyFileInputTree pyFileInputTree); + + void visitIfStatement(PyIfStatementTree pyIfStatementTree); + + void visitElseStatement(PyElseStatementTree pyElseStatementTree); + + void visitExecStatement(PyExecStatementTree pyExecStatementTree); + + void visitAssertStatement(PyAssertStatementTree pyAssertStatementTree); + + void visitDelStatement(PyDelStatementTree pyDelStatementTree); + + void visitPassStatement(PyPassStatementTree pyPassStatementTree); + + void visitPrintStatement(PyPrintStatementTree pyPrintStatementTree); + + void visitReturnStatement(PyReturnStatementTree pyReturnStatementTree); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 1a4f6e5995..de7e6cd8e7 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -19,10 +19,17 @@ */ package org.sonar.python.api.tree; +import com.sonar.sslr.api.AstNode; + public interface Tree { - enum Kind { + void accept(PyTreeVisitor visitor); + + boolean is(Kind kind); + + AstNode astNode(); + enum Kind { ASSERT_STMT(PyAssertStatementTree.class), DEL_STMT(PyDelStatementTree.class), @@ -35,7 +42,12 @@ enum Kind { PRINT_STMT(PyPrintStatementTree.class), - RETURN_STMT(PyReturnStatementTree.class); + RETURN_STMT(PyReturnStatementTree.class), + + IF_STATEMENT(PyIfStatementTree.class), + + ELSE_STATEMENT(PyElseStatementTree.class), + ; final Class associatedInterface; 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 e44bcc9b1c..b3cddbad57 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 @@ -35,7 +35,7 @@ public class FileMetrics { private List functionComplexities = new ArrayList<>(); public FileMetrics(PythonVisitorContext context, boolean ignoreHeaderComments) { - AstNode rootTree = context.rootTree(); + AstNode rootTree = (AstNode) context.rootTree(); numberOfStatements = rootTree.getDescendants(PythonGrammar.STATEMENT).size(); numberOfClasses = rootTree.getDescendants(PythonGrammar.CLASSDEF).size(); complexityVisitor.scanFile(context); diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java new file mode 100644 index 0000000000..f24bd247d2 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -0,0 +1,104 @@ +/* + * 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.tree; + +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.python.api.tree.PyAssertStatementTree; +import org.sonar.python.api.tree.PyDelStatementTree; +import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyExecStatementTree; +import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyPassStatementTree; +import org.sonar.python.api.tree.PyPrintStatementTree; +import org.sonar.python.api.tree.PyReturnStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.Tree; + +/** + * Default implementation of {@link org.sonar.python.api.tree.PyTreeVisitor}. + */ +public class BaseTreeVisitor implements PyTreeVisitor { + + protected void scan(@Nullable Tree tree) { + if (tree != null) { + tree.accept(this); + } + } + + protected void scan(List trees) { + if (trees != null) { + for (Tree tree : trees) { + scan(tree); + } + } + } + + @Override + public void visitFileInput(PyFileInputTree pyFileInputTree) { + scan(pyFileInputTree.statements()); + } + + @Override + public void visitIfStatement(PyIfStatementTree pyIfStatementTree) { + scan(pyIfStatementTree.condition()); + scan(pyIfStatementTree.body()); + scan(pyIfStatementTree.elifBranches()); + scan(pyIfStatementTree.elseBranch()); + } + + @Override + public void visitElseStatement(PyElseStatementTree pyElseStatementTree) { + scan(pyElseStatementTree.body()); + } + + @Override + public void visitExecStatement(PyExecStatementTree pyExecStatementTree) { + scan(pyExecStatementTree.expression()); + scan(pyExecStatementTree.globalsExpression()); + scan(pyExecStatementTree.localsExpression()); + } + + @Override + public void visitAssertStatement(PyAssertStatementTree pyAssertStatementTree) { + scan(pyAssertStatementTree.expressions()); + } + + @Override + public void visitDelStatement(PyDelStatementTree pyDelStatementTree) { + scan(pyDelStatementTree.expressions()); + } + + @Override + public void visitPassStatement(PyPassStatementTree pyPassStatementTree) { + // nothing to visit for pass statement + } + + @Override + public void visitPrintStatement(PyPrintStatementTree pyPrintStatementTree) { + scan(pyPrintStatementTree.expressions()); + } + + @Override + public void visitReturnStatement(PyReturnStatementTree pyReturnStatementTree) { + scan(pyReturnStatementTree.expressions()); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyAssertStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyAssertStatementTreeImpl.java index eaaa591c8b..6332d89fa8 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyAssertStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyAssertStatementTreeImpl.java @@ -24,6 +24,7 @@ import java.util.List; import org.sonar.python.api.tree.PyAssertStatementTree; import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyTreeVisitor; public class PyAssertStatementTreeImpl extends PyTree implements PyAssertStatementTree { private final Token assertKeyword; @@ -49,4 +50,9 @@ public List expressions() { public Kind getKind() { return Kind.ASSERT_STMT; } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitAssertStatement(this); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyDelStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyDelStatementTreeImpl.java index 9eaf1d3bac..1079aba3b3 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyDelStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyDelStatementTreeImpl.java @@ -24,6 +24,7 @@ import java.util.List; import org.sonar.python.api.tree.PyDelStatementTree; import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyTreeVisitor; public class PyDelStatementTreeImpl extends PyTree implements PyDelStatementTree { private final Token delKeyword; @@ -49,4 +50,9 @@ public List expressions() { public Kind getKind() { return Kind.DEL_STMT; } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitDelStatement(this); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java index aa47805e04..e3851123f5 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java @@ -24,6 +24,8 @@ import java.util.List; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.Tree; public class PyElseStatementTreeImpl extends PyTree implements PyElseStatementTree { private final Token elseKeyword; @@ -37,7 +39,7 @@ public PyElseStatementTreeImpl(AstNode astNode, Token elseKeyword, List body() { return body; } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitElseStatement(this); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExecStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExecStatementTreeImpl.java index f59cdb308a..f2705b403f 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyExecStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExecStatementTreeImpl.java @@ -23,6 +23,7 @@ import com.sonar.sslr.api.Token; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyTreeVisitor; public class PyExecStatementTreeImpl extends PyTree implements PyExecStatementTree { private final Token execKeyword; @@ -70,4 +71,9 @@ public PyExpressionTree localsExpression() { public Kind getKind() { return Kind.EXEC_STMT; } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitExecStatement(this); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java index 6c563b9e19..9cee6fab0e 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionTreeImpl.java @@ -21,7 +21,9 @@ import com.sonar.sslr.api.AstNode; import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyTreeVisitor; +// FIXME : this class is a placeholder while we implement concrete type of expressions and should be deleted. public class PyExpressionTreeImpl extends PyTree implements PyExpressionTree { public PyExpressionTreeImpl(AstNode astNode) { super(astNode); @@ -32,4 +34,8 @@ public Kind getKind() { return null; } + @Override + public void accept(PyTreeVisitor visitor) { + //TODO : remove + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java index eebb43ab3f..2afb9ffd3f 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyFileInputTreeImpl.java @@ -23,6 +23,7 @@ import java.util.List; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; import org.sonar.python.api.tree.Tree; public class PyFileInputTreeImpl extends PyTree implements PyFileInputTree { @@ -43,4 +44,9 @@ public Kind getKind() { public List statements() { return statements; } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitFileInput(this); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java index f19f057cd7..e87d3fce8f 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyIfStatementTreeImpl.java @@ -28,6 +28,8 @@ import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.Tree; +import org.sonar.python.api.tree.PyTreeVisitor; public class PyIfStatementTreeImpl extends PyTree implements PyIfStatementTree { @@ -99,6 +101,11 @@ public PyElseStatementTree elseBranch() { @Override public Kind getKind() { - return null; + return Tree.Kind.IF_STATEMENT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitIfStatement(this); } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyPassStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyPassStatementTreeImpl.java index 01798a7cc3..dae6437085 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyPassStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyPassStatementTreeImpl.java @@ -22,6 +22,7 @@ import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; import org.sonar.python.api.tree.PyPassStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; public class PyPassStatementTreeImpl extends PyTree implements PyPassStatementTree { private final Token passKeyword; @@ -40,4 +41,9 @@ public Token passKeyword() { public Kind getKind() { return Kind.PASS_STMT; } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitPassStatement(this); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java index 332247682e..e6843c80f4 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyPrintStatementTreeImpl.java @@ -24,6 +24,7 @@ import java.util.List; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyPrintStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; public class PyPrintStatementTreeImpl extends PyTree implements PyPrintStatementTree { private final Token printKeyword; @@ -49,4 +50,9 @@ public List expressions() { public Kind getKind() { return Kind.PRINT_STMT; } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitPrintStatement(this); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyReturnStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyReturnStatementTreeImpl.java index 1576c6fee2..930b7321dd 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyReturnStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyReturnStatementTreeImpl.java @@ -24,6 +24,7 @@ import java.util.List; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyReturnStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; public class PyReturnStatementTreeImpl extends PyTree implements PyReturnStatementTree { private final Token returnKeyword; @@ -49,4 +50,9 @@ public List expressions() { public Kind getKind() { return Kind.RETURN_STMT; } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitReturnStatement(this); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyTree.java b/python-squid/src/main/java/org/sonar/python/tree/PyTree.java index 5aa3fcf00a..9b160d6e91 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyTree.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyTree.java @@ -34,4 +34,14 @@ public PyTree(AstNode node) { } public abstract Kind getKind(); + + @Override + public boolean is(Kind kind) { + return kind == getKind(); + } + + @Override + public AstNode astNode() { + return node; + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index aa644a30dc..c79e9be29c 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -48,6 +48,8 @@ public PyFileInputTree fileInput(AstNode astNode) { private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.IF_STMT)) { return ifStatement(astNode); + } else if (astNode.is(PythonGrammar.PASS_STMT)) { + return passStatement(astNode); } if (astNode.is(PythonGrammar.PRINT_STMT)) { return printStatement(astNode); 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 2c380a5ed5..8761f95a5d 100644 --- a/python-squid/src/test/java/org/sonar/python/PythonCheckTest.java +++ b/python-squid/src/test/java/org/sonar/python/PythonCheckTest.java @@ -36,7 +36,7 @@ public class PythonCheckTest { private static final File FILE = new File("src/test/resources/file.py"); public static final String MESSAGE = "message"; - private static List scanFileForIssues(File file, PythonCheck check) { + private static List scanFileForIssues(File file, PythonCheckAstNode check) { PythonVisitorContext context = TestPythonVisitorRunner.createContext(file); check.scanFile(context); return context.getIssues(); @@ -151,7 +151,7 @@ public void visitFile(AstNode astNode) { assertThat(issue.primaryLocation().endLine()).isEqualTo(3); } - private static class TestPythonCheck extends PythonCheck { + private static class TestPythonCheck extends PythonCheckAstNode { @Override public Set subscribedKinds() { 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 4d7eab3556..cb16445125 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 @@ -31,7 +31,7 @@ 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.PythonCheck; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.PythonVisitor; import org.sonar.python.TokenLocation; import org.sonar.python.api.PythonGrammar; @@ -98,7 +98,7 @@ public PythonHighlighter(SensorContext context, InputFile inputFile) { @Override public Set subscribedKinds() { - return PythonCheck.immutableSet( + return PythonCheckAstNode.immutableSet( PythonGrammar.FUNCDEF, PythonGrammar.CLASSDEF, PythonGrammar.FILE_INPUT); 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 5d0ca74e2f..0644a34b54 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 @@ -42,6 +42,7 @@ import org.sonar.python.IssueLocation; import org.sonar.python.PythonCheck; import org.sonar.python.PythonCheck.PreciseIssue; +import org.sonar.python.PythonCheckAstNode; import org.sonar.python.PythonConfiguration; import org.sonar.python.PythonFile; import org.sonar.python.PythonVisitorContext; 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 9741b84bb5..45caeb84d1 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 @@ -40,7 +40,7 @@ public PythonCpdAnalyzer(SensorContext context) { } public void pushCpdTokens(InputFile inputFile, PythonVisitorContext visitorContext) { - AstNode root = visitorContext.rootTree(); + AstNode root = (AstNode) visitorContext.rootTree(); if (root != null) { NewCpdTokens cpdTokens = context.newCpdTokens().onFile(inputFile); List tokens = root.getTokens(); From 691a6b32c3b60da25f7693e70af96290c9a55c1f Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 15:35:44 +0200 Subject: [PATCH 15/35] Add PyYieldStatement and PyYieldExpression --- .../checks/CollapsibleIfStatementsCheck.java | 2 +- .../sonar/python/api/tree/PyTreeVisitor.java | 4 ++ .../api/tree/PyYieldExpressionTree.java | 33 +++++++++ .../python/api/tree/PyYieldStatementTree.java | 24 +++++++ .../java/org/sonar/python/api/tree/Tree.java | 9 ++- .../sonar/python/tree/BaseTreeVisitor.java | 12 ++++ .../python/tree/PyElseStatementTreeImpl.java | 2 +- .../python/tree/PyIfStatementTreeImpl.java | 2 +- .../tree/PyYieldExpressionTreeImpl.java | 67 +++++++++++++++++++ .../python/tree/PyYieldStatementTreeImpl.java | 49 ++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 24 +++++++ .../python/api/tree/PythonTreeMakerTest.java | 34 ++++++++++ 12 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyYieldExpressionTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyYieldStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyYieldExpressionTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyYieldStatementTreeImpl.java 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 8896631cc7..55cd74d77d 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 @@ -56,7 +56,7 @@ public void visitIfStatement(PyIfStatementTree ifStatement) { && ifStatement.elseBranch() == null && ifStatement.elifBranches().isEmpty() && statements.size() == 1 - && statements.get(0).is(Tree.Kind.IF_STATEMENT)) { + && statements.get(0).is(Tree.Kind.IF_STMT)) { PyIfStatementTree singleIfChild = (PyIfStatementTree) statements.get(0); if (singleIfChild.isElif() || singleIfChild.elseBranch() != null || !singleIfChild.elifBranches().isEmpty()) { return; diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 01fcdca3ef..128cbacfe6 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -38,4 +38,8 @@ public interface PyTreeVisitor { void visitPrintStatement(PyPrintStatementTree pyPrintStatementTree); void visitReturnStatement(PyReturnStatementTree pyReturnStatementTree); + + void visitYieldStatement(PyYieldStatementTree pyYieldStatementTree); + + void visitYieldExpression(PyYieldExpressionTree pyYieldExpressionTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyYieldExpressionTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyYieldExpressionTree.java new file mode 100644 index 0000000000..d82ff95567 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyYieldExpressionTree.java @@ -0,0 +1,33 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +public interface PyYieldExpressionTree extends PyExpressionTree { + Token yieldKeyword(); + + @CheckForNull + Token fromKeyword(); + + List expressions(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyYieldStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyYieldStatementTree.java new file mode 100644 index 0000000000..2369b8b6d7 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyYieldStatementTree.java @@ -0,0 +1,24 @@ +/* + * 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.api.tree; + +public interface PyYieldStatementTree extends PyStatementTree { + PyYieldExpressionTree yieldExpression(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index de7e6cd8e7..3d4bdd4522 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -34,20 +34,23 @@ enum Kind { DEL_STMT(PyDelStatementTree.class), + ELSE_STMT(PyElseStatementTree.class), + EXEC_STMT(PyExecStatementTree.class), FILE_INPUT(PyFileInputTree.class), + IF_STMT(PyIfStatementTree.class), + PASS_STMT(PyPassStatementTree.class), PRINT_STMT(PyPrintStatementTree.class), RETURN_STMT(PyReturnStatementTree.class), - IF_STATEMENT(PyIfStatementTree.class), + YIELD_EXPR(PyYieldExpressionTree.class), - ELSE_STATEMENT(PyElseStatementTree.class), - ; + YIELD_STMT(PyYieldStatementTree.class); final Class associatedInterface; diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index f24bd247d2..eb821c0388 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -31,6 +31,8 @@ import org.sonar.python.api.tree.PyPrintStatementTree; import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.PyYieldExpressionTree; +import org.sonar.python.api.tree.PyYieldStatementTree; import org.sonar.python.api.tree.Tree; /** @@ -101,4 +103,14 @@ public void visitPrintStatement(PyPrintStatementTree pyPrintStatementTree) { public void visitReturnStatement(PyReturnStatementTree pyReturnStatementTree) { scan(pyReturnStatementTree.expressions()); } + + @Override + public void visitYieldStatement(PyYieldStatementTree pyYieldStatementTree) { + scan(pyYieldStatementTree.yieldExpression()); + } + + @Override + public void visitYieldExpression(PyYieldExpressionTree pyYieldExpressionTree) { + scan(pyYieldExpressionTree.expressions()); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java index e3851123f5..cd9abcdb40 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyElseStatementTreeImpl.java @@ -39,7 +39,7 @@ public PyElseStatementTreeImpl(AstNode astNode, Token elseKeyword, List expressionTrees; + + public PyYieldExpressionTreeImpl(AstNode astNode, Token yieldKeyword, Token fromKeyword, List expressionTrees) { + super(astNode); + this.yieldKeyword = yieldKeyword; + this.fromKeyword = fromKeyword; + this.expressionTrees = expressionTrees; + } + + @Override + public Token yieldKeyword() { + return yieldKeyword; + } + + @CheckForNull + @Override + public Token fromKeyword() { + return fromKeyword; + } + + @Override + public List expressions() { + return expressionTrees; + } + + @Override + public Kind getKind() { + return Kind.YIELD_EXPR; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitYieldExpression(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyYieldStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyYieldStatementTreeImpl.java new file mode 100644 index 0000000000..aa86ea8740 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyYieldStatementTreeImpl.java @@ -0,0 +1,49 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.PyYieldExpressionTree; +import org.sonar.python.api.tree.PyYieldStatementTree; + +public class PyYieldStatementTreeImpl extends PyTree implements PyYieldStatementTree { + private final PyYieldExpressionTree yieldExpression; + + public PyYieldStatementTreeImpl(AstNode astNode, PyYieldExpressionTree yieldExpression) { + super(astNode); + this.yieldExpression = yieldExpression; + } + + @Override + public PyYieldExpressionTree yieldExpression() { + return yieldExpression; + } + + @Override + public Kind getKind() { + return Kind.YIELD_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitYieldStatement(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index c79e9be29c..2da1a58d1c 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -37,6 +37,8 @@ import org.sonar.python.api.tree.PyPrintStatementTree; import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyYieldExpressionTree; +import org.sonar.python.api.tree.PyYieldStatementTree; public class PythonTreeMaker { @@ -69,6 +71,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.RETURN_STMT)) { return returnStatement(astNode); } + if (astNode.is(PythonGrammar.YIELD_STMT)) { + return yieldStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -144,6 +149,22 @@ public PyReturnStatementTree returnStatement(AstNode astNode) { return new PyReturnStatementTreeImpl(astNode, astNode.getTokens().get(0), expressionTrees); } + public PyYieldStatementTree yieldStatement(AstNode astNode) { + return new PyYieldStatementTreeImpl(astNode, yieldExpression(astNode.getFirstChild(PythonGrammar.YIELD_EXPR))); + } + + public PyYieldExpressionTree yieldExpression(AstNode astNode) { + Token yieldKeyword = astNode.getFirstChild(PythonKeyword.YIELD).getToken(); + AstNode nodeContainingExpression = astNode; + AstNode fromKeyword = astNode.getFirstChild(PythonKeyword.FROM); + if (fromKeyword == null) { + nodeContainingExpression = astNode.getFirstChild(PythonGrammar.TESTLIST); + } + List expressionTrees = nodeContainingExpression.getChildren(PythonGrammar.TEST).stream() + .map(this::expression) + .collect(Collectors.toList()); + return new PyYieldExpressionTreeImpl(astNode, yieldKeyword, fromKeyword == null ? null : fromKeyword.getToken(), expressionTrees); + } // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { @@ -180,6 +201,9 @@ private PyElseStatementTree elseStatement(AstNode astNode) { } PyExpressionTree expression(AstNode astNode) { + if (astNode.is(PythonGrammar.YIELD_EXPR)) { + return yieldExpression(astNode); + } return new PyExpressionTreeImpl(astNode); } } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index fc18a533da..3a03a415d8 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -64,6 +64,11 @@ public void fileInputTreeOnEmptyFile() { pyTree = new PythonTreeMaker().fileInput(astNode); assertThat(pyTree.statements()).hasSize(1); assertThat(pyTree.statements().get(0)).isInstanceOf(PyReturnStatementTree.class); + + astNode = p.parse("yield foo"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyYieldStatementTree.class); } @Test @@ -233,4 +238,33 @@ public void returnStatement() { assertThat(returnStatement.returnKeyword().getValue()).isEqualTo("return"); assertThat(returnStatement.expressions()).hasSize(0); } + + @Test + public void yieldStatement() { + setRootRule(PythonGrammar.YIELD_STMT); + AstNode astNode = p.parse("yield foo"); + PyYieldStatementTree yieldStatement = new PythonTreeMaker().yieldStatement(astNode); + assertThat(yieldStatement).isNotNull(); + PyYieldExpressionTree yieldExpression = yieldStatement.yieldExpression(); + assertThat(yieldExpression).isInstanceOf(PyYieldExpressionTree.class); + assertThat(yieldExpression.expressions()).hasSize(1); + + astNode = p.parse("yield foo, bar"); + yieldStatement = new PythonTreeMaker().yieldStatement(astNode); + assertThat(yieldStatement).isNotNull(); + yieldExpression = yieldStatement.yieldExpression(); + assertThat(yieldExpression).isInstanceOf(PyYieldExpressionTree.class); + assertThat(yieldExpression.yieldKeyword().getValue()).isEqualTo("yield"); + assertThat(yieldExpression.fromKeyword()).isNull(); + assertThat(yieldExpression.expressions()).hasSize(2); + + astNode = p.parse("yield from foo"); + yieldStatement = new PythonTreeMaker().yieldStatement(astNode); + assertThat(yieldStatement).isNotNull(); + yieldExpression = yieldStatement.yieldExpression(); + assertThat(yieldExpression).isInstanceOf(PyYieldExpressionTree.class); + assertThat(yieldExpression.yieldKeyword().getValue()).isEqualTo("yield"); + assertThat(yieldExpression.fromKeyword().getValue()).isEqualTo("from"); + assertThat(yieldExpression.expressions()).hasSize(1); + } } From 04ec86514ed16163258e6e28efdfd57035fc9e69 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 16:33:08 +0200 Subject: [PATCH 16/35] Add PyRaiseStatement --- .../python/api/tree/PyRaiseStatementTree.java | 36 +++++++++ .../sonar/python/api/tree/PyTreeVisitor.java | 2 + .../java/org/sonar/python/api/tree/Tree.java | 2 + .../sonar/python/tree/BaseTreeVisitor.java | 7 ++ .../python/tree/PyRaiseStatementTreeImpl.java | 75 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 22 ++++++ .../python/api/tree/PythonTreeMakerTest.java | 41 ++++++++++ 7 files changed, 185 insertions(+) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyRaiseStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyRaiseStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyRaiseStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyRaiseStatementTree.java new file mode 100644 index 0000000000..1c664f15ef --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyRaiseStatementTree.java @@ -0,0 +1,36 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +public interface PyRaiseStatementTree extends PyStatementTree { + Token raiseKeyword(); + + @CheckForNull + Token fromKeyword(); + + @CheckForNull + PyExpressionTree fromExpression(); + + List expressions(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 128cbacfe6..285c09f1d7 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -42,4 +42,6 @@ public interface PyTreeVisitor { void visitYieldStatement(PyYieldStatementTree pyYieldStatementTree); void visitYieldExpression(PyYieldExpressionTree pyYieldExpressionTree); + + void visitRaiseStatement(PyRaiseStatementTree pyRaiseStatementTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 3d4bdd4522..776dae9a45 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -46,6 +46,8 @@ enum Kind { PRINT_STMT(PyPrintStatementTree.class), + RAISE_STMT(PyRaiseStatementTree.class), + RETURN_STMT(PyReturnStatementTree.class), YIELD_EXPR(PyYieldExpressionTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index eb821c0388..2e06999e36 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -29,6 +29,7 @@ import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; +import org.sonar.python.api.tree.PyRaiseStatementTree; import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyTreeVisitor; import org.sonar.python.api.tree.PyYieldExpressionTree; @@ -113,4 +114,10 @@ public void visitYieldStatement(PyYieldStatementTree pyYieldStatementTree) { public void visitYieldExpression(PyYieldExpressionTree pyYieldExpressionTree) { scan(pyYieldExpressionTree.expressions()); } + + @Override + public void visitRaiseStatement(PyRaiseStatementTree pyRaiseStatementTree) { + scan(pyRaiseStatementTree.expressions()); + scan(pyRaiseStatementTree.fromExpression()); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyRaiseStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyRaiseStatementTreeImpl.java new file mode 100644 index 0000000000..9e39f9b648 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyRaiseStatementTreeImpl.java @@ -0,0 +1,75 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyRaiseStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyRaiseStatementTreeImpl extends PyTree implements PyRaiseStatementTree { + private final Token raiseKeyword; + private final List expressions; + private final Token fromKeyword; + private final PyExpressionTree fromExpression; + + public PyRaiseStatementTreeImpl(AstNode astNode, Token raiseKeyword, List expressions, Token fromKeyword, PyExpressionTree fromExpression) { + super(astNode); + this.raiseKeyword = raiseKeyword; + this.expressions = expressions; + this.fromKeyword = fromKeyword; + this.fromExpression = fromExpression; + } + + @Override + public Token raiseKeyword() { + return raiseKeyword; + } + + @CheckForNull + @Override + public Token fromKeyword() { + return fromKeyword; + } + + @CheckForNull + @Override + public PyExpressionTree fromExpression() { + return fromExpression; + } + + @Override + public List expressions() { + return expressions; + } + + @Override + public Kind getKind() { + return Kind.RAISE_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 2da1a58d1c..916746a391 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -21,6 +21,7 @@ import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -35,6 +36,7 @@ import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; +import org.sonar.python.api.tree.PyRaiseStatementTree; import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyStatementTree; import org.sonar.python.api.tree.PyYieldExpressionTree; @@ -74,6 +76,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.YIELD_STMT)) { return yieldStatement(astNode); } + if (astNode.is(PythonGrammar.RAISE_STMT)) { + return raiseStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -165,6 +170,23 @@ public PyYieldExpressionTree yieldExpression(AstNode astNode) { .collect(Collectors.toList()); return new PyYieldExpressionTreeImpl(astNode, yieldKeyword, fromKeyword == null ? null : fromKeyword.getToken(), expressionTrees); } + + public PyRaiseStatementTree raiseStatement(AstNode astNode) { + AstNode fromKeyword = astNode.getFirstChild(PythonKeyword.FROM); + List expressions = new ArrayList<>(); + AstNode fromExpression = null; + if (fromKeyword != null) { + expressions.add(astNode.getFirstChild(PythonGrammar.TEST)); + fromExpression = astNode.getLastChild(PythonGrammar.TEST); + } else { + expressions = astNode.getChildren(PythonGrammar.TEST); + } + List expressionTrees = expressions.stream() + .map(this::expression) + .collect(Collectors.toList()); + return new PyRaiseStatementTreeImpl(astNode, astNode.getFirstChild(PythonKeyword.RAISE).getToken(), + expressionTrees, fromKeyword == null ? null : fromKeyword.getToken(), fromExpression == null ? null : expression(fromExpression)); + } // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 3a03a415d8..e9a41d6503 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -69,6 +69,11 @@ public void fileInputTreeOnEmptyFile() { pyTree = new PythonTreeMaker().fileInput(astNode); assertThat(pyTree.statements()).hasSize(1); assertThat(pyTree.statements().get(0)).isInstanceOf(PyYieldStatementTree.class); + + astNode = p.parse("raise foo"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyRaiseStatementTree.class); } @Test @@ -267,4 +272,40 @@ public void yieldStatement() { assertThat(yieldExpression.fromKeyword().getValue()).isEqualTo("from"); assertThat(yieldExpression.expressions()).hasSize(1); } + + @Test + public void raiseStatement() { + setRootRule(PythonGrammar.RAISE_STMT); + AstNode astNode = p.parse("raise foo"); + PyRaiseStatementTree raiseStatement = new PythonTreeMaker().raiseStatement(astNode); + assertThat(raiseStatement).isNotNull(); + assertThat(raiseStatement.raiseKeyword().getValue()).isEqualTo("raise"); + assertThat(raiseStatement.fromKeyword()).isNull(); + assertThat(raiseStatement.fromExpression()).isNull(); + assertThat(raiseStatement.expressions()).hasSize(1); + + astNode = p.parse("raise foo, bar"); + raiseStatement = new PythonTreeMaker().raiseStatement(astNode); + assertThat(raiseStatement).isNotNull(); + assertThat(raiseStatement.raiseKeyword().getValue()).isEqualTo("raise"); + assertThat(raiseStatement.fromKeyword()).isNull(); + assertThat(raiseStatement.fromExpression()).isNull(); + assertThat(raiseStatement.expressions()).hasSize(2); + + astNode = p.parse("raise foo from bar"); + raiseStatement = new PythonTreeMaker().raiseStatement(astNode); + assertThat(raiseStatement).isNotNull(); + assertThat(raiseStatement.raiseKeyword().getValue()).isEqualTo("raise"); + assertThat(raiseStatement.fromKeyword().getValue()).isEqualTo("from"); + assertThat(raiseStatement.fromExpression()).isNotNull(); + assertThat(raiseStatement.expressions()).hasSize(1); + + astNode = p.parse("raise"); + raiseStatement = new PythonTreeMaker().raiseStatement(astNode); + assertThat(raiseStatement).isNotNull(); + assertThat(raiseStatement.raiseKeyword().getValue()).isEqualTo("raise"); + assertThat(raiseStatement.fromKeyword()).isNull(); + assertThat(raiseStatement.fromExpression()).isNull(); + assertThat(raiseStatement.expressions()).isEmpty(); + } } From d9994ddb91f836b302b661633abcfb499f1be302 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Tue, 20 Aug 2019 17:51:09 +0200 Subject: [PATCH 17/35] Add PyBreakStatement and PyContinueStatement --- .../python/api/tree/PyBreakStatementTree.java | 26 ++++++++++ .../api/tree/PyContinueStatementTree.java | 26 ++++++++++ .../sonar/python/api/tree/PyTreeVisitor.java | 4 ++ .../java/org/sonar/python/api/tree/Tree.java | 4 ++ .../sonar/python/tree/BaseTreeVisitor.java | 12 +++++ .../python/tree/PyBreakStatementTreeImpl.java | 49 +++++++++++++++++++ .../tree/PyContinueStatementTreeImpl.java | 49 +++++++++++++++++++ .../python/tree/PyRaiseStatementTreeImpl.java | 2 +- .../sonar/python/tree/PythonTreeMaker.java | 17 +++++++ .../python/api/tree/PythonTreeMakerTest.java | 28 +++++++++++ 10 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyBreakStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyContinueStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyBreakStatementTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyContinueStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyBreakStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyBreakStatementTree.java new file mode 100644 index 0000000000..bb6aaab9a6 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyBreakStatementTree.java @@ -0,0 +1,26 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; + +public interface PyBreakStatementTree extends PyStatementTree { + Token breakKeyword(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyContinueStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyContinueStatementTree.java new file mode 100644 index 0000000000..fdc3494af9 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyContinueStatementTree.java @@ -0,0 +1,26 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; + +public interface PyContinueStatementTree extends PyStatementTree { + Token continueKeyword(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 285c09f1d7..17caae6415 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -44,4 +44,8 @@ public interface PyTreeVisitor { void visitYieldExpression(PyYieldExpressionTree pyYieldExpressionTree); void visitRaiseStatement(PyRaiseStatementTree pyRaiseStatementTree); + + void visitBreakStatement(PyBreakStatementTree pyBreakStatementTree); + + void visitContinueStatement(PyContinueStatementTree pyContinueStatementTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 776dae9a45..00c641f3e7 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -32,6 +32,10 @@ public interface Tree { enum Kind { ASSERT_STMT(PyAssertStatementTree.class), + BREAK_STMT(PyBreakStatementTree.class), + + CONTINUE_STMT(PyContinueStatementTree.class), + DEL_STMT(PyDelStatementTree.class), ELSE_STMT(PyElseStatementTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 2e06999e36..0ad86f030f 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -22,6 +22,8 @@ import java.util.List; import javax.annotation.Nullable; import org.sonar.python.api.tree.PyAssertStatementTree; +import org.sonar.python.api.tree.PyBreakStatementTree; +import org.sonar.python.api.tree.PyContinueStatementTree; import org.sonar.python.api.tree.PyDelStatementTree; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; @@ -120,4 +122,14 @@ public void visitRaiseStatement(PyRaiseStatementTree pyRaiseStatementTree) { scan(pyRaiseStatementTree.expressions()); scan(pyRaiseStatementTree.fromExpression()); } + + @Override + public void visitBreakStatement(PyBreakStatementTree pyBreakStatementTree) { + // nothing to visit for break statement + } + + @Override + public void visitContinueStatement(PyContinueStatementTree pyContinueStatementTree) { + // nothing to visit for continue statement + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyBreakStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyBreakStatementTreeImpl.java new file mode 100644 index 0000000000..a4bc59a1f1 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyBreakStatementTreeImpl.java @@ -0,0 +1,49 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import org.sonar.python.api.tree.PyBreakStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyBreakStatementTreeImpl extends PyTree implements PyBreakStatementTree { + private final Token breakKeyword; + + public PyBreakStatementTreeImpl(AstNode astNode, Token breakKeyword) { + super(astNode); + this.breakKeyword = breakKeyword; + } + + @Override + public Token breakKeyword() { + return breakKeyword; + } + + @Override + public Kind getKind() { + return Kind.BREAK_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitBreakStatement(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyContinueStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyContinueStatementTreeImpl.java new file mode 100644 index 0000000000..5f0bcf9ff6 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyContinueStatementTreeImpl.java @@ -0,0 +1,49 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import org.sonar.python.api.tree.PyContinueStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyContinueStatementTreeImpl extends PyTree implements PyContinueStatementTree { + private final Token continueKeyword; + + public PyContinueStatementTreeImpl(AstNode astNode, Token continueKeyword) { + super(astNode); + this.continueKeyword = continueKeyword; + } + + @Override + public Token continueKeyword() { + return continueKeyword; + } + + @Override + public Kind getKind() { + return Kind.CONTINUE_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitContinueStatement(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyRaiseStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyRaiseStatementTreeImpl.java index 9e39f9b648..4a3bdfdf68 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyRaiseStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyRaiseStatementTreeImpl.java @@ -70,6 +70,6 @@ public Kind getKind() { @Override public void accept(PyTreeVisitor visitor) { - + visitor.visitRaiseStatement(this); } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 916746a391..c0b63d1a6b 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -28,6 +28,8 @@ import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; import org.sonar.python.api.tree.PyAssertStatementTree; +import org.sonar.python.api.tree.PyBreakStatementTree; +import org.sonar.python.api.tree.PyContinueStatementTree; import org.sonar.python.api.tree.PyDelStatementTree; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; @@ -79,6 +81,13 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.RAISE_STMT)) { return raiseStatement(astNode); } + if (astNode.is(PythonGrammar.BREAK_STMT)) { + return breakStatement(astNode); + } + if (astNode.is(PythonGrammar.CONTINUE_STMT)) { + return continueStatement(astNode); + } + // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -187,6 +196,14 @@ public PyRaiseStatementTree raiseStatement(AstNode astNode) { return new PyRaiseStatementTreeImpl(astNode, astNode.getFirstChild(PythonKeyword.RAISE).getToken(), expressionTrees, fromKeyword == null ? null : fromKeyword.getToken(), fromExpression == null ? null : expression(fromExpression)); } + + public PyBreakStatementTree breakStatement(AstNode astNode) { + return new PyBreakStatementTreeImpl(astNode, astNode.getToken()); + } + + public PyContinueStatementTree continueStatement(AstNode astNode) { + return new PyContinueStatementTreeImpl(astNode, astNode.getToken()); + } // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index e9a41d6503..8917af84de 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -74,6 +74,16 @@ public void fileInputTreeOnEmptyFile() { pyTree = new PythonTreeMaker().fileInput(astNode); assertThat(pyTree.statements()).hasSize(1); assertThat(pyTree.statements().get(0)).isInstanceOf(PyRaiseStatementTree.class); + + astNode = p.parse("break"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyBreakStatementTree.class); + + astNode = p.parse("continue"); + pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).isInstanceOf(PyContinueStatementTree.class); } @Test @@ -308,4 +318,22 @@ public void raiseStatement() { assertThat(raiseStatement.fromExpression()).isNull(); assertThat(raiseStatement.expressions()).isEmpty(); } + + @Test + public void breakStatement() { + setRootRule(PythonGrammar.BREAK_STMT); + AstNode astNode = p.parse("break"); + PyBreakStatementTree breakStatement = new PythonTreeMaker().breakStatement(astNode); + assertThat(breakStatement).isNotNull(); + assertThat(breakStatement.breakKeyword().getValue()).isEqualTo("break"); + } + + @Test + public void continueStatement() { + setRootRule(PythonGrammar.CONTINUE_STMT); + AstNode astNode = p.parse("continue"); + PyContinueStatementTree continueStatement = new PythonTreeMaker().continueStatement(astNode); + assertThat(continueStatement).isNotNull(); + assertThat(continueStatement.continueKeyword().getValue()).isEqualTo("continue"); + } } From 71bdfb58801af692d9eb60319bf9b07fb3d54387 Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Wed, 21 Aug 2019 09:37:25 +0200 Subject: [PATCH 18/35] Function definition statement --- .../python/api/tree/PyDecoratorTree.java | 23 ++++ .../python/api/tree/PyFunctionDefTree.java | 61 +++++++++ .../org/sonar/python/api/tree/PyNameTree.java | 25 ++++ .../sonar/python/api/tree/PyTreeVisitor.java | 4 + .../python/api/tree/PyTypedArgListTree.java | 23 ++++ .../java/org/sonar/python/api/tree/Tree.java | 4 + .../sonar/python/tree/BaseTreeVisitor.java | 16 +++ .../python/tree/PyFunctionDefTreeImpl.java | 120 ++++++++++++++++++ .../org/sonar/python/tree/PyNameTreeImpl.java | 48 +++++++ .../sonar/python/tree/PythonTreeMaker.java | 29 ++++- .../python/api/tree/PythonTreeMakerTest.java | 12 ++ 11 files changed, 360 insertions(+), 5 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyDecoratorTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyFunctionDefTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyNameTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyTypedArgListTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyFunctionDefTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyNameTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyDecoratorTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyDecoratorTree.java new file mode 100644 index 0000000000..db0c101ce5 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyDecoratorTree.java @@ -0,0 +1,23 @@ +/* + * 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.api.tree; + +public interface PyDecoratorTree extends Tree { +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyFunctionDefTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyFunctionDefTree.java new file mode 100644 index 0000000000..36eaf77661 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyFunctionDefTree.java @@ -0,0 +1,61 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +public interface PyFunctionDefTree extends Tree { + + List decorators(); + + Token defKeyword(); + + @CheckForNull + Token asyncKeyword(); + + PyNameTree name(); + + Token leftPar(); + + PyTypedArgListTree typedArgs(); + + Token rightPar(); + + /** + * dash of optional '->' + */ + @CheckForNull + Token dash(); + /** + * pointer of optional '->' + */ + @CheckForNull + Token gt(); + + @CheckForNull + PyExpressionTree annotationReturn(); + + Token colon(); + + List body(); + +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyNameTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyNameTree.java new file mode 100644 index 0000000000..bd0e47d976 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyNameTree.java @@ -0,0 +1,25 @@ +/* + * 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.api.tree; + +public interface PyNameTree extends Tree { + + String name(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 17caae6415..3c89094f3f 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -48,4 +48,8 @@ public interface PyTreeVisitor { void visitBreakStatement(PyBreakStatementTree pyBreakStatementTree); void visitContinueStatement(PyContinueStatementTree pyContinueStatementTree); + + void visitFunctionDef(PyFunctionDefTree pyFunctionDefTree); + + void visitName(PyNameTree pyNameTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTypedArgListTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTypedArgListTree.java new file mode 100644 index 0000000000..1e73a55de1 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTypedArgListTree.java @@ -0,0 +1,23 @@ +/* + * 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.api.tree; + +public interface PyTypedArgListTree extends Tree { +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 00c641f3e7..bc4991e524 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -44,8 +44,12 @@ enum Kind { FILE_INPUT(PyFileInputTree.class), + FUNCDEF(PyFunctionDefTree.class), + IF_STMT(PyIfStatementTree.class), + NAME(PyNameTree.class), + PASS_STMT(PyPassStatementTree.class), PRINT_STMT(PyPrintStatementTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 0ad86f030f..3f2815d499 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -28,7 +28,9 @@ import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyFunctionDefTree; import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyNameTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; import org.sonar.python.api.tree.PyRaiseStatementTree; @@ -132,4 +134,18 @@ public void visitBreakStatement(PyBreakStatementTree pyBreakStatementTree) { public void visitContinueStatement(PyContinueStatementTree pyContinueStatementTree) { // nothing to visit for continue statement } + + @Override + public void visitFunctionDef(PyFunctionDefTree pyFunctionDefTree) { + scan(pyFunctionDefTree.decorators()); + scan(pyFunctionDefTree.name()); + scan(pyFunctionDefTree.typedArgs()); + scan(pyFunctionDefTree.annotationReturn()); + scan(pyFunctionDefTree.body()); + } + + @Override + public void visitName(PyNameTree pyNameTree) { + // nothing to scan on a name + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyFunctionDefTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyFunctionDefTreeImpl.java new file mode 100644 index 0000000000..9110e1e87f --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyFunctionDefTreeImpl.java @@ -0,0 +1,120 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyDecoratorTree; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyFunctionDefTree; +import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.PyTypedArgListTree; + +public class PyFunctionDefTreeImpl extends PyTree implements PyFunctionDefTree { + + private final PyNameTree name; + private final PyTypedArgListTree typedArgs; + private final List body; + + public PyFunctionDefTreeImpl(AstNode astNode, PyNameTree name, PyTypedArgListTree typedArgs, List body) { + super(astNode); + this.name = name; + this.typedArgs = typedArgs; + this.body = body; + } + + @Override + public List decorators() { + return null; + } + + @Override + public Token defKeyword() { + return null; + } + + @CheckForNull + @Override + public Token asyncKeyword() { + return null; + } + + @Override + public PyNameTree name() { + return name; + } + + @Override + public Token leftPar() { + return null; + } + + @Override + public PyTypedArgListTree typedArgs() { + return typedArgs; + } + + @Override + public Token rightPar() { + return null; + } + + @CheckForNull + @Override + public Token dash() { + return null; + } + + @CheckForNull + @Override + public Token gt() { + return null; + } + + @CheckForNull + @Override + public PyExpressionTree annotationReturn() { + return null; + } + + @Override + public Token colon() { + return null; + } + + @Override + public List body() { + return body; + } + + @Override + public Kind getKind() { + return Kind.FUNCDEF; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitFunctionDef(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyNameTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyNameTreeImpl.java new file mode 100644 index 0000000000..4224afaf65 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyNameTreeImpl.java @@ -0,0 +1,48 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyNameTreeImpl extends PyTree implements PyNameTree { + private final String name; + + public PyNameTreeImpl(AstNode astNode, String name) { + super(astNode); + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public Kind getKind() { + return Kind.NAME; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitName(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index c0b63d1a6b..b97c7c10e4 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -20,6 +20,7 @@ package org.sonar.python.tree; import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.GenericTokenType; import com.sonar.sslr.api.Token; import java.util.ArrayList; import java.util.Collections; @@ -35,12 +36,15 @@ import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyFunctionDefTree; import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyNameTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; import org.sonar.python.api.tree.PyRaiseStatementTree; import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTypedArgListTree; import org.sonar.python.api.tree.PyYieldExpressionTree; import org.sonar.python.api.tree.PyYieldStatementTree; @@ -92,16 +96,18 @@ private PyStatementTree statement(AstNode astNode) { return null; } - private List getStatementsFromSuite(AstNode astNode) { + private List getStatementsFromSuite(AstNode astNode) { if (astNode.is(PythonGrammar.SUITE)) { List statements = getStatements(astNode); if (statements.isEmpty()) { AstNode stmtListNode = astNode.getFirstChild(PythonGrammar.STMT_LIST); return stmtListNode.getChildren(PythonGrammar.SIMPLE_STMT).stream() .map(AstNode::getFirstChild) + .map(this::statement) .collect(Collectors.toList()); } - return statements; + return statements.stream().map(this::statement) + .collect(Collectors.toList()); } return Collections.emptyList(); } @@ -210,7 +216,7 @@ public PyIfStatementTree ifStatement(AstNode astNode) { Token ifToken = astNode.getTokens().get(0); AstNode condition = astNode.getFirstChild(PythonGrammar.TEST); AstNode suite = astNode.getFirstChild(PythonGrammar.SUITE); - List statements = getStatementsFromSuite(suite).stream().map(this::statement).collect(Collectors.toList()); + List statements = getStatementsFromSuite(suite); AstNode elseSuite = astNode.getLastChild(PythonGrammar.SUITE); PyElseStatementTree elseStatement = null; if (elseSuite.getPreviousSibling().getPreviousSibling().is(PythonKeyword.ELSE)) { @@ -228,14 +234,14 @@ private PyIfStatementTree elifStatement(AstNode astNode) { Token elifToken = astNode.getToken(); AstNode suite = astNode.getNextSibling().getNextSibling().getNextSibling(); AstNode condition = astNode.getNextSibling(); - List statements = getStatementsFromSuite(suite).stream().map(this::statement).collect(Collectors.toList()); + List statements = getStatementsFromSuite(suite); return new PyIfStatementTreeImpl( astNode, elifToken, expression(condition), statements); } private PyElseStatementTree elseStatement(AstNode astNode) { Token elseToken = astNode.getPreviousSibling().getPreviousSibling().getToken(); - List statements = getStatementsFromSuite(astNode).stream().map(this::statement).collect(Collectors.toList()); + List statements = getStatementsFromSuite(astNode); return new PyElseStatementTreeImpl(astNode, elseToken, statements); } @@ -245,4 +251,17 @@ PyExpressionTree expression(AstNode astNode) { } return new PyExpressionTreeImpl(astNode); } + + public PyFunctionDefTree funcdefStatement(AstNode astNode) { + + PyNameTree name = name(astNode.getFirstChild(PythonGrammar.FUNCNAME).getFirstChild(PythonGrammar.NAME)); + + PyTypedArgListTree typedArgs = null; + List body = getStatementsFromSuite(astNode.getFirstChild(PythonGrammar.SUITE)); + return new PyFunctionDefTreeImpl(astNode, name, typedArgs, body); + } + + private PyNameTree name(AstNode astNode) { + return new PyNameTreeImpl(astNode, astNode.getFirstChild(GenericTokenType.IDENTIFIER).getTokenOriginalValue()); + } } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 8917af84de..e4aeb59c08 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -336,4 +336,16 @@ public void continueStatement() { assertThat(continueStatement).isNotNull(); assertThat(continueStatement.continueKeyword().getValue()).isEqualTo("continue"); } + + @Test + public void funcdef_statement() { + setRootRule(PythonGrammar.FUNCDEF); + AstNode astNode = p.parse("def func(): pass"); + PyFunctionDefTree functionDefTree = new PythonTreeMaker().funcdefStatement(astNode); + assertThat(functionDefTree.name()).isNotNull(); + assertThat(functionDefTree.name().name()).isEqualTo("func"); + assertThat(functionDefTree.body()).hasSize(1); + assertThat(functionDefTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(functionDefTree.typedArgs()).isNull(); + } } From 298f92086e561c22d1d30b0d8386ac336c87f68c Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Wed, 21 Aug 2019 10:11:31 +0200 Subject: [PATCH 19/35] Make PyfunctionDefTree a statement --- .../java/org/sonar/python/PythonVisitor.java | 2 +- .../sonar/python/PythonVisitorContext.java | 16 +++- .../sonar/python/TestPythonVisitorRunner.java | 6 +- .../python/api/tree/PyFunctionDefTree.java | 2 +- .../org/sonar/python/metrics/FileMetrics.java | 2 +- .../java/org/sonar/python/tree/PyTree.java | 3 - .../sonar/python/tree/PythonTreeMaker.java | 17 ++-- .../python/api/tree/PythonTreeMakerTest.java | 80 +++++++------------ .../sonar/plugins/python/PythonScanner.java | 9 ++- .../plugins/python/cpd/PythonCpdAnalyzer.java | 2 +- 10 files changed, 67 insertions(+), 72 deletions(-) diff --git a/python-squid/src/main/java/org/sonar/python/PythonVisitor.java b/python-squid/src/main/java/org/sonar/python/PythonVisitor.java index 4fb2b4ae95..3cc131d4ba 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/PythonVisitor.java @@ -60,7 +60,7 @@ public PythonVisitorContext getContext() { public void scanFile(PythonVisitorContext context) { this.context = context; - AstNode tree = (AstNode) context.rootTree(); + AstNode tree = context.rootAstNode(); if (tree != null) { visitFile(tree); scanNode(tree, subscribedKinds()); 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 199e700124..b608333810 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java +++ b/python-squid/src/main/java/org/sonar/python/PythonVisitorContext.java @@ -19,6 +19,7 @@ */ package org.sonar.python; +import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.RecognitionException; import java.util.ArrayList; import java.util.List; @@ -33,21 +34,24 @@ public class PythonVisitorContext { private final PyFileInputTreeImpl rootTree; private final PythonFile pythonFile; private final RecognitionException parsingException; + private final AstNode rootAst; private SymbolTable symbolTable = null; private List issues = new ArrayList<>(); - public PythonVisitorContext(PyFileInputTree rootTree, PythonFile pythonFile) { - this(rootTree, pythonFile, null); + + public PythonVisitorContext(AstNode rootAst, PyFileInputTree rootTree, PythonFile pythonFile) { + this(rootAst, rootTree, pythonFile, null); SymbolTableBuilderVisitor symbolTableBuilderVisitor = new SymbolTableBuilderVisitor(); symbolTableBuilderVisitor.scanFile(this); symbolTable = symbolTableBuilderVisitor.symbolTable(); } public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException) { - this(null, pythonFile, parsingException); + this(null, null, pythonFile, parsingException); } - private PythonVisitorContext(PyFileInputTree rootTree, PythonFile pythonFile, RecognitionException parsingException) { + private PythonVisitorContext(AstNode rootAst, PyFileInputTree rootTree, PythonFile pythonFile, RecognitionException parsingException) { + this.rootAst = rootAst; this.rootTree = (PyFileInputTreeImpl) rootTree; this.pythonFile = pythonFile; this.parsingException = parsingException; @@ -57,6 +61,10 @@ public PyFileInputTree rootTree() { return rootTree; } + public AstNode rootAstNode() { + return rootAst; + } + public PythonFile pythonFile() { return pythonFile; } diff --git a/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java b/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java index bd1798507a..de4601919b 100644 --- a/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java +++ b/python-squid/src/main/java/org/sonar/python/TestPythonVisitorRunner.java @@ -19,6 +19,7 @@ */ package org.sonar.python; +import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Grammar; import com.sonar.sslr.impl.Parser; import java.io.File; @@ -44,8 +45,9 @@ public static void scanFile(File file, PythonVisitor... visitors) { public static PythonVisitorContext createContext(File file) { Parser parser = PythonParser.create(new PythonConfiguration(StandardCharsets.UTF_8)); TestPythonFile pythonFile = new TestPythonFile(file); - PyFileInputTree rootTree = new PythonTreeMaker().fileInput(parser.parse(pythonFile.content())); - return new PythonVisitorContext(rootTree, pythonFile); + AstNode astNode = parser.parse(pythonFile.content()); + PyFileInputTree rootTree = new PythonTreeMaker().fileInput(astNode); + return new PythonVisitorContext(astNode, rootTree, pythonFile); } private static class TestPythonFile implements PythonFile { diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyFunctionDefTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyFunctionDefTree.java index 36eaf77661..b5c095052f 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyFunctionDefTree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyFunctionDefTree.java @@ -23,7 +23,7 @@ import java.util.List; import javax.annotation.CheckForNull; -public interface PyFunctionDefTree extends Tree { +public interface PyFunctionDefTree extends PyStatementTree { List decorators(); 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 b3cddbad57..238f222677 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 @@ -35,7 +35,7 @@ public class FileMetrics { private List functionComplexities = new ArrayList<>(); public FileMetrics(PythonVisitorContext context, boolean ignoreHeaderComments) { - AstNode rootTree = (AstNode) context.rootTree(); + AstNode rootTree = context.rootAstNode(); numberOfStatements = rootTree.getDescendants(PythonGrammar.STATEMENT).size(); numberOfClasses = rootTree.getDescendants(PythonGrammar.CLASSDEF).size(); complexityVisitor.scanFile(context); diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyTree.java b/python-squid/src/main/java/org/sonar/python/tree/PyTree.java index 9b160d6e91..236c48fb37 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyTree.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyTree.java @@ -28,9 +28,6 @@ public abstract class PyTree extends AstNode implements Tree { public PyTree(AstNode node) { super(node.getType(), node.getName(), node.getToken()); this.node = node; - for (AstNode child : node.getChildren()) { - addChild(child); - } } public abstract Kind getKind(); diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index b97c7c10e4..ca5b9001ba 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -58,7 +58,8 @@ public PyFileInputTree fileInput(AstNode astNode) { private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.IF_STMT)) { return ifStatement(astNode); - } else if (astNode.is(PythonGrammar.PASS_STMT)) { + } + if (astNode.is(PythonGrammar.PASS_STMT)) { return passStatement(astNode); } if (astNode.is(PythonGrammar.PRINT_STMT)) { @@ -91,6 +92,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.CONTINUE_STMT)) { return continueStatement(astNode); } + if (astNode.is(PythonGrammar.FUNCDEF)) { + return funcDefStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; @@ -180,9 +184,12 @@ public PyYieldExpressionTree yieldExpression(AstNode astNode) { if (fromKeyword == null) { nodeContainingExpression = astNode.getFirstChild(PythonGrammar.TESTLIST); } - List expressionTrees = nodeContainingExpression.getChildren(PythonGrammar.TEST).stream() - .map(this::expression) - .collect(Collectors.toList()); + List expressionTrees = Collections.emptyList(); + if (nodeContainingExpression != null) { + expressionTrees = nodeContainingExpression.getChildren(PythonGrammar.TEST).stream() + .map(this::expression) + .collect(Collectors.toList()); + } return new PyYieldExpressionTreeImpl(astNode, yieldKeyword, fromKeyword == null ? null : fromKeyword.getToken(), expressionTrees); } @@ -252,7 +259,7 @@ PyExpressionTree expression(AstNode astNode) { return new PyExpressionTreeImpl(astNode); } - public PyFunctionDefTree funcdefStatement(AstNode astNode) { + public PyFunctionDefTree funcDefStatement(AstNode astNode) { PyNameTree name = name(astNode.getFirstChild(PythonGrammar.FUNCNAME).getFirstChild(PythonGrammar.NAME)); diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index e4aeb59c08..af80beca69 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -20,6 +20,8 @@ package org.sonar.python.api.tree; import com.sonar.sslr.api.AstNode; +import java.util.HashMap; +import java.util.Map; import org.junit.Test; import org.sonar.python.api.PythonGrammar; import org.sonar.python.parser.RuleTest; @@ -34,56 +36,30 @@ public void fileInputTreeOnEmptyFile() { AstNode astNode = p.parse(""); PyFileInputTree pyTree = new PythonTreeMaker().fileInput(astNode); assertThat(pyTree.statements()).isEmpty(); + } - astNode = p.parse("pass"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyPassStatementTree.class); - - astNode = p.parse("print 'foo'"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyPrintStatementTree.class); - - astNode = p.parse("exec foo"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyExecStatementTree.class); - - astNode = p.parse("assert foo"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyAssertStatementTree.class); - - astNode = p.parse("del foo"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyDelStatementTree.class); - - astNode = p.parse("return foo"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyReturnStatementTree.class); - - astNode = p.parse("yield foo"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyYieldStatementTree.class); - - astNode = p.parse("raise foo"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyRaiseStatementTree.class); - - astNode = p.parse("break"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyBreakStatementTree.class); - - astNode = p.parse("continue"); - pyTree = new PythonTreeMaker().fileInput(astNode); - assertThat(pyTree.statements()).hasSize(1); - assertThat(pyTree.statements().get(0)).isInstanceOf(PyContinueStatementTree.class); + @Test + public void verify_expected_statement() { + Map> testData = new HashMap<>(); + testData.put("pass", PyPassStatementTree.class); + testData.put("print 'foo'", PyPrintStatementTree.class); + testData.put("exec foo", PyExecStatementTree.class); + testData.put("assert foo", PyAssertStatementTree.class); + testData.put("del foo", PyDelStatementTree.class); + testData.put("return foo", PyReturnStatementTree.class); + testData.put("yield foo", PyYieldStatementTree.class); + testData.put("raise foo", PyRaiseStatementTree.class); + testData.put("break", PyBreakStatementTree.class); + testData.put("continue", PyContinueStatementTree.class); + testData.put("def foo():pass", PyFunctionDefTree.class); + + + testData.forEach((c,clazz) -> { + AstNode astNode = p.parse(c); + PyFileInputTree pyTree = new PythonTreeMaker().fileInput(astNode); + assertThat(pyTree.statements()).hasSize(1); + assertThat(pyTree.statements().get(0)).as(c).isInstanceOf(clazz); + }); } @Test @@ -281,6 +257,10 @@ public void yieldStatement() { assertThat(yieldExpression.yieldKeyword().getValue()).isEqualTo("yield"); assertThat(yieldExpression.fromKeyword().getValue()).isEqualTo("from"); assertThat(yieldExpression.expressions()).hasSize(1); + + astNode = p.parse("yield"); + yieldStatement = new PythonTreeMaker().yieldStatement(astNode); + assertThat(yieldStatement).isNotNull(); } @Test @@ -341,7 +321,7 @@ public void continueStatement() { public void funcdef_statement() { setRootRule(PythonGrammar.FUNCDEF); AstNode astNode = p.parse("def func(): pass"); - PyFunctionDefTree functionDefTree = new PythonTreeMaker().funcdefStatement(astNode); + PyFunctionDefTree functionDefTree = new PythonTreeMaker().funcDefStatement(astNode); assertThat(functionDefTree.name()).isNotNull(); assertThat(functionDefTree.name().name()).isEqualTo("func"); assertThat(functionDefTree.body()).hasSize(1); 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 0644a34b54..95288d7ee1 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,6 +19,7 @@ */ package org.sonar.plugins.python; +import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Grammar; import com.sonar.sslr.api.RecognitionException; import com.sonar.sslr.impl.Parser; @@ -42,15 +43,14 @@ import org.sonar.python.IssueLocation; import org.sonar.python.PythonCheck; import org.sonar.python.PythonCheck.PreciseIssue; -import org.sonar.python.PythonCheckAstNode; import org.sonar.python.PythonConfiguration; import org.sonar.python.PythonFile; import org.sonar.python.PythonVisitorContext; import org.sonar.python.api.tree.PyFileInputTree; -import org.sonar.python.tree.PythonTreeMaker; import org.sonar.python.metrics.FileLinesVisitor; import org.sonar.python.metrics.FileMetrics; import org.sonar.python.parser.PythonParser; +import org.sonar.python.tree.PythonTreeMaker; public class PythonScanner { @@ -92,8 +92,9 @@ private void scanFile(InputFile inputFile) { PythonFile pythonFile = SonarQubePythonFile.create(inputFile); PythonVisitorContext visitorContext; try { - PyFileInputTree parse = new PythonTreeMaker().fileInput(parser.parse(pythonFile.content())); - visitorContext = new PythonVisitorContext(parse, pythonFile); + AstNode astNode = parser.parse(pythonFile.content()); + PyFileInputTree parse = new PythonTreeMaker().fileInput(astNode); + visitorContext = new PythonVisitorContext(astNode, parse, pythonFile); saveMeasures(inputFile, visitorContext); } catch (RecognitionException e) { visitorContext = new PythonVisitorContext(pythonFile, e); 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 45caeb84d1..576bcc0dae 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 @@ -40,7 +40,7 @@ public PythonCpdAnalyzer(SensorContext context) { } public void pushCpdTokens(InputFile inputFile, PythonVisitorContext visitorContext) { - AstNode root = (AstNode) visitorContext.rootTree(); + AstNode root = visitorContext.rootAstNode(); if (root != null) { NewCpdTokens cpdTokens = context.newCpdTokens().onFile(inputFile); List tokens = root.getTokens(); From c83180d7f02b08549fadd33fd6fac95cb8dd557b Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Wed, 21 Aug 2019 15:35:14 +0200 Subject: [PATCH 20/35] Class definition tree --- .../sonar/python/api/tree/PyArgListTree.java | 23 +++++ .../sonar/python/api/tree/PyClassDefTree.java | 46 +++++++++ .../sonar/python/api/tree/PyTreeVisitor.java | 2 + .../java/org/sonar/python/api/tree/Tree.java | 2 + .../sonar/python/tree/BaseTreeVisitor.java | 8 ++ .../sonar/python/tree/PyClassDefTreeImpl.java | 98 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 19 +++- .../python/api/tree/PythonTreeMakerTest.java | 15 +++ 8 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyArgListTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyClassDefTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyClassDefTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyArgListTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyArgListTree.java new file mode 100644 index 0000000000..bee30279fd --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyArgListTree.java @@ -0,0 +1,23 @@ +/* + * 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.api.tree; + +public interface PyArgListTree extends Tree { +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyClassDefTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyClassDefTree.java new file mode 100644 index 0000000000..f29da34262 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyClassDefTree.java @@ -0,0 +1,46 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +public interface PyClassDefTree extends PyStatementTree { + + List decorators(); + + Token classKeyword(); + + PyNameTree name(); + + @CheckForNull + Token leftPar(); + + @CheckForNull + PyArgListTree args(); + + @CheckForNull + Token rightPar(); + + Token colon(); + + List body(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 3c89094f3f..0e8003b880 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -52,4 +52,6 @@ public interface PyTreeVisitor { void visitFunctionDef(PyFunctionDefTree pyFunctionDefTree); void visitName(PyNameTree pyNameTree); + + void visitClassDef(PyClassDefTree pyClassDefTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index bc4991e524..f1642f22eb 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -34,6 +34,8 @@ enum Kind { BREAK_STMT(PyBreakStatementTree.class), + CLASSDEF(PyClassDefTree.class), + CONTINUE_STMT(PyContinueStatementTree.class), DEL_STMT(PyDelStatementTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 3f2815d499..82838a919e 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -23,6 +23,7 @@ import javax.annotation.Nullable; import org.sonar.python.api.tree.PyAssertStatementTree; import org.sonar.python.api.tree.PyBreakStatementTree; +import org.sonar.python.api.tree.PyClassDefTree; import org.sonar.python.api.tree.PyContinueStatementTree; import org.sonar.python.api.tree.PyDelStatementTree; import org.sonar.python.api.tree.PyElseStatementTree; @@ -148,4 +149,11 @@ public void visitFunctionDef(PyFunctionDefTree pyFunctionDefTree) { public void visitName(PyNameTree pyNameTree) { // nothing to scan on a name } + + @Override + public void visitClassDef(PyClassDefTree pyClassDefTree) { + scan(pyClassDefTree.name()); + scan(pyClassDefTree.args()); + scan(pyClassDefTree.body()); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyClassDefTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyClassDefTreeImpl.java new file mode 100644 index 0000000000..69a4373e08 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyClassDefTreeImpl.java @@ -0,0 +1,98 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyArgListTree; +import org.sonar.python.api.tree.PyClassDefTree; +import org.sonar.python.api.tree.PyDecoratorTree; +import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyClassDefTreeImpl extends PyTree implements PyClassDefTree { + + private final PyNameTree name; + private final PyArgListTree args; + private final List body; + + public PyClassDefTreeImpl(AstNode astNode, PyNameTree name, PyArgListTree args, List body) { + super(astNode); + this.name = name; + this.args = args; + this.body = body; + } + + @Override + public Kind getKind() { + return Kind.CLASSDEF; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitClassDef(this); + } + + @Override + public List decorators() { + return null; + } + + @Override + public Token classKeyword() { + return null; + } + + @Override + public PyNameTree name() { + return name; + } + + @CheckForNull + @Override + public Token leftPar() { + return null; + } + + @CheckForNull + @Override + public PyArgListTree args() { + return args; + } + + @CheckForNull + @Override + public Token rightPar() { + return null; + } + + @Override + public Token colon() { + return null; + } + + @Override + public List body() { + return body; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index ca5b9001ba..149cff6933 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -28,8 +28,10 @@ import java.util.stream.Collectors; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; +import org.sonar.python.api.tree.PyArgListTree; import org.sonar.python.api.tree.PyAssertStatementTree; import org.sonar.python.api.tree.PyBreakStatementTree; +import org.sonar.python.api.tree.PyClassDefTree; import org.sonar.python.api.tree.PyContinueStatementTree; import org.sonar.python.api.tree.PyDelStatementTree; import org.sonar.python.api.tree.PyElseStatementTree; @@ -95,7 +97,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.FUNCDEF)) { return funcDefStatement(astNode); } - + if (astNode.is(PythonGrammar.CLASSDEF)) { + return classDefStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -260,14 +264,23 @@ PyExpressionTree expression(AstNode astNode) { } public PyFunctionDefTree funcDefStatement(AstNode astNode) { - + // TODO decorators PyNameTree name = name(astNode.getFirstChild(PythonGrammar.FUNCNAME).getFirstChild(PythonGrammar.NAME)); - + // TODO argList PyTypedArgListTree typedArgs = null; List body = getStatementsFromSuite(astNode.getFirstChild(PythonGrammar.SUITE)); return new PyFunctionDefTreeImpl(astNode, name, typedArgs, body); } + public PyClassDefTree classDefStatement(AstNode astNode) { + // TODO decorators + PyNameTree name = name(astNode.getFirstChild(PythonGrammar.CLASSNAME).getFirstChild(PythonGrammar.NAME)); + // TODO argList + PyArgListTree args = null; + List body = getStatementsFromSuite(astNode.getFirstChild(PythonGrammar.SUITE)); + return new PyClassDefTreeImpl(astNode, name, args, body); + } + private PyNameTree name(AstNode astNode) { return new PyNameTreeImpl(astNode, astNode.getFirstChild(GenericTokenType.IDENTIFIER).getTokenOriginalValue()); } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index af80beca69..7630e85fe1 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -327,5 +327,20 @@ public void funcdef_statement() { assertThat(functionDefTree.body()).hasSize(1); assertThat(functionDefTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); assertThat(functionDefTree.typedArgs()).isNull(); + assertThat(functionDefTree.decorators()).isNull(); + + } + + @Test + public void classdef_statement() { + setRootRule(PythonGrammar.CLASSDEF); + AstNode astNode = p.parse("class clazz: pass"); + PyClassDefTree classDefTree = new PythonTreeMaker().classDefStatement(astNode); + assertThat(classDefTree.name()).isNotNull(); + assertThat(classDefTree.name().name()).isEqualTo("clazz"); + assertThat(classDefTree.body()).hasSize(1); + assertThat(classDefTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(classDefTree.args()).isNull(); + assertThat(classDefTree.decorators()).isNull(); } } From 38adacf702407813d810bae18926514c6ed212e1 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Wed, 21 Aug 2019 16:03:10 +0200 Subject: [PATCH 21/35] Add ImportName, ImportFrom, AliasedName, DottedName --- .../org/sonar/python/api/PythonGrammar.java | 10 +- .../python/api/tree/PyAliasedNameTree.java | 40 +++++++ .../python/api/tree/PyDottedNameTree.java | 27 +++++ .../python/api/tree/PyImportFromTree.java | 51 ++++++++ .../python/api/tree/PyImportNameTree.java | 36 ++++++ .../api/tree/PyImportStatementTree.java | 27 +++++ .../sonar/python/api/tree/PyTreeVisitor.java | 8 ++ .../java/org/sonar/python/api/tree/Tree.java | 10 ++ .../sonar/python/tree/BaseTreeVisitor.java | 27 +++++ .../python/tree/PyAliasedNameTreeImpl.java | 69 +++++++++++ .../python/tree/PyDottedNameTreeImpl.java | 50 ++++++++ .../python/tree/PyImportFromTreeImpl.java | 100 ++++++++++++++++ .../python/tree/PyImportNameTreeImpl.java | 59 +++++++++ .../sonar/python/tree/PythonTreeMaker.java | 74 ++++++++++++ .../python/api/tree/PythonTreeMakerTest.java | 113 ++++++++++++++++++ 15 files changed, 699 insertions(+), 2 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyAliasedNameTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyDottedNameTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyImportFromTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyImportNameTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyImportStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyAliasedNameTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyDottedNameTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyImportFromTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyImportNameTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/PythonGrammar.java b/python-squid/src/main/java/org/sonar/python/api/PythonGrammar.java index 07d58646a7..fc42d1108d 100644 --- a/python-squid/src/main/java/org/sonar/python/api/PythonGrammar.java +++ b/python-squid/src/main/java/org/sonar/python/api/PythonGrammar.java @@ -351,11 +351,17 @@ public static void simpleStatements(LexerfulGrammarBuilder b) { b.rule(IMPORT_STMT).is(b.firstOf(IMPORT_NAME, IMPORT_FROM)); b.rule(IMPORT_NAME).is("import", DOTTED_AS_NAMES); - b.rule(IMPORT_FROM).is("from", b.firstOf(b.sequence(b.zeroOrMore("."), DOTTED_NAME), b.oneOrMore(".")), "import", + b.rule(IMPORT_FROM).is( + "from", + b.firstOf( + b.sequence(b.zeroOrMore("."), DOTTED_NAME), + b.oneOrMore(".")), + "import", b.firstOf("*", b.sequence("(", IMPORT_AS_NAMES, ")"), IMPORT_AS_NAMES)); b.rule(IMPORT_AS_NAME).is(NAME, b.optional("as", NAME)); b.rule(DOTTED_AS_NAME).is(DOTTED_NAME, b.optional("as", NAME)); - b.rule(IMPORT_AS_NAMES).is(IMPORT_AS_NAME, b.zeroOrMore(",", IMPORT_AS_NAME), b.optional(",")); + b.rule(IMPORT_AS_NAMES).is(IMPORT_AS_NAME, b.zeroOrMore(",", IMPORT_AS_NAME), + b.optional(",")); b.rule(DOTTED_AS_NAMES).is(DOTTED_AS_NAME, b.zeroOrMore(",", DOTTED_AS_NAME)); b.rule(GLOBAL_STMT).is("global", NAME, b.zeroOrMore(",", NAME)); diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyAliasedNameTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyAliasedNameTree.java new file mode 100644 index 0000000000..339afe09fc --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyAliasedNameTree.java @@ -0,0 +1,40 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import javax.annotation.CheckForNull; + +/** + * Aliased name + * + *
+ * {@link PyNameTree#name()} {@link #asKeyword()} {@link #alias()}
+ * 
+ */ +public interface PyAliasedNameTree extends Tree { + @CheckForNull + Token asKeyword(); + + @CheckForNull + PyNameTree alias(); + + PyDottedNameTree dottedName(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyDottedNameTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyDottedNameTree.java new file mode 100644 index 0000000000..4af1b51c3d --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyDottedNameTree.java @@ -0,0 +1,27 @@ +/* + * 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.api.tree; + +import java.util.List; + +public interface PyDottedNameTree extends Tree { + + List names(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyImportFromTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyImportFromTree.java new file mode 100644 index 0000000000..0ee4d316e6 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyImportFromTree.java @@ -0,0 +1,51 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +/** + * Import From statement + * + *
+ *   {@link #fromKeyword()} {@link #dottedPrefixForModule()} {@link #module()} {@link #importKeyword()} {@link #importedNames()}
+ * 
+ */ +public interface PyImportFromTree extends PyImportStatementTree { + Token fromKeyword(); + + @CheckForNull + PyDottedNameTree module(); + + Token importKeyword(); + + @CheckForNull + List dottedPrefixForModule(); + + @CheckForNull + List importedNames(); + + boolean isWildcardImport(); + + @CheckForNull + Token wildcard(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyImportNameTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyImportNameTree.java new file mode 100644 index 0000000000..f2c0b86a56 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyImportNameTree.java @@ -0,0 +1,36 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +/** + * Import statement + * + *
+ *   {@link #importKeyword()} {@link #modules()}}
+ * 
+ */ +public interface PyImportNameTree extends PyImportStatementTree { + Token importKeyword(); + + List modules(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyImportStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyImportStatementTree.java new file mode 100644 index 0000000000..0b0a00241e --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyImportStatementTree.java @@ -0,0 +1,27 @@ +/* + * 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.api.tree; + +/** + * Import statement + * + */ +public interface PyImportStatementTree extends PyStatementTree { +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 0e8003b880..c337928c77 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -54,4 +54,12 @@ public interface PyTreeVisitor { void visitName(PyNameTree pyNameTree); void visitClassDef(PyClassDefTree pyClassDefTree); + + void visitAliasedName(PyAliasedNameTree pyAliasedNameTree); + + void visitDottedName(PyDottedNameTree pyDottedNameTree); + + void visitImportFrom(PyImportFromTree pyImportFromTree); + + void visitImportName(PyImportNameTree pyImportNameTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index f1642f22eb..8e1dcbf12d 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -30,6 +30,8 @@ public interface Tree { AstNode astNode(); enum Kind { + ALIASED_NAME(PyAliasedNameTree.class), + ASSERT_STMT(PyAssertStatementTree.class), BREAK_STMT(PyBreakStatementTree.class), @@ -40,6 +42,8 @@ enum Kind { DEL_STMT(PyDelStatementTree.class), + DOTTED_NAME(PyDottedNameTree.class), + ELSE_STMT(PyElseStatementTree.class), EXEC_STMT(PyExecStatementTree.class), @@ -50,6 +54,12 @@ enum Kind { IF_STMT(PyIfStatementTree.class), + IMPORT_FROM(PyImportFromTree.class), + + IMPORT_NAME(PyDottedNameTree.class), + + IMPORT_STMT(PyDottedNameTree.class), + NAME(PyNameTree.class), PASS_STMT(PyPassStatementTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 82838a919e..c61e64c69a 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -21,16 +21,20 @@ import java.util.List; import javax.annotation.Nullable; +import org.sonar.python.api.tree.PyAliasedNameTree; import org.sonar.python.api.tree.PyAssertStatementTree; import org.sonar.python.api.tree.PyBreakStatementTree; import org.sonar.python.api.tree.PyClassDefTree; import org.sonar.python.api.tree.PyContinueStatementTree; import org.sonar.python.api.tree.PyDelStatementTree; +import org.sonar.python.api.tree.PyDottedNameTree; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyFunctionDefTree; import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyImportFromTree; +import org.sonar.python.api.tree.PyImportNameTree; import org.sonar.python.api.tree.PyNameTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; @@ -156,4 +160,27 @@ public void visitClassDef(PyClassDefTree pyClassDefTree) { scan(pyClassDefTree.args()); scan(pyClassDefTree.body()); } + + @Override + public void visitAliasedName(PyAliasedNameTree pyAliasedNameTree) { + scan(pyAliasedNameTree.dottedName()); + scan(pyAliasedNameTree.alias()); + } + + @Override + public void visitDottedName(PyDottedNameTree pyDottedNameTree) { + scan(pyDottedNameTree.names()); + } + + @Override + public void visitImportFrom(PyImportFromTree pyImportFromTree) { + scan(pyImportFromTree.module()); + scan(pyImportFromTree.importedNames()); + } + + @Override + public void visitImportName(PyImportNameTree pyImportNameTree) { + scan(pyImportNameTree.modules()); + } + } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyAliasedNameTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyAliasedNameTreeImpl.java new file mode 100644 index 0000000000..0c51107eb1 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyAliasedNameTreeImpl.java @@ -0,0 +1,69 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyAliasedNameTree; +import org.sonar.python.api.tree.PyDottedNameTree; +import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyAliasedNameTreeImpl extends PyTree implements PyAliasedNameTree { + + private final Token asKeyword; + private final PyDottedNameTree dottedName; + private final PyNameTree alias; + + public PyAliasedNameTreeImpl(AstNode astNode, Token asKeyword, PyDottedNameTree dottedName, PyNameTree alias) { + super(astNode); + this.asKeyword = asKeyword; + this.dottedName = dottedName; + this.alias = alias; + } + + @CheckForNull + @Override + public Token asKeyword() { + return asKeyword; + } + + @CheckForNull + @Override + public PyNameTree alias() { + return alias; + } + + @Override + public PyDottedNameTree dottedName() { + return dottedName; + } + + @Override + public Kind getKind() { + return Kind.ALIASED_NAME; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitAliasedName(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyDottedNameTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyDottedNameTreeImpl.java new file mode 100644 index 0000000000..290993f3f1 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyDottedNameTreeImpl.java @@ -0,0 +1,50 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import java.util.List; +import org.sonar.python.api.tree.PyDottedNameTree; +import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyDottedNameTreeImpl extends PyTree implements PyDottedNameTree { + private final List names; + + public PyDottedNameTreeImpl(AstNode astNode, List names) { + super(astNode); + this.names = names; + } + + @Override + public List names() { + return names; + } + + @Override + public Kind getKind() { + return Kind.DOTTED_NAME; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitDottedName(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyImportFromTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyImportFromTreeImpl.java new file mode 100644 index 0000000000..eed6814828 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyImportFromTreeImpl.java @@ -0,0 +1,100 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.PythonPunctuator; +import org.sonar.python.api.tree.PyAliasedNameTree; +import org.sonar.python.api.tree.PyDottedNameTree; +import org.sonar.python.api.tree.PyImportFromTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyImportFromTreeImpl extends PyTree implements PyImportFromTree { + private final Token fromKeyword; + private final List dottedPrefixForModule; + private final PyDottedNameTree moduleName; + private final Token importKeyword; + private final List aliasedImportNames; + private final boolean isWildcardImport; + private final Token wildcard; + + public PyImportFromTreeImpl(AstNode astNode, Token fromKeyword, List dottedPrefixForModule, PyDottedNameTree moduleName, Token importKeyword, List aliasedImportNames, boolean isWildcardImport) { + super(astNode); + this.fromKeyword = fromKeyword; + this.dottedPrefixForModule = dottedPrefixForModule; + this.moduleName = moduleName; + this.importKeyword = importKeyword; + this.aliasedImportNames = aliasedImportNames; + this.isWildcardImport = isWildcardImport; + this.wildcard = isWildcardImport ? astNode.getFirstChild(PythonPunctuator.MUL).getToken() : null; + } + + @Override + public Token fromKeyword() { + return fromKeyword; + } + + @CheckForNull + @Override + public PyDottedNameTree module() { + return moduleName; + } + + @Override + public Token importKeyword() { + return importKeyword; + } + + @CheckForNull + @Override + public List dottedPrefixForModule() { + return dottedPrefixForModule; + } + + @CheckForNull + @Override + public List importedNames() { + return aliasedImportNames; + } + + @Override + public boolean isWildcardImport() { + return isWildcardImport; + } + + @CheckForNull + @Override + public Token wildcard() { + return wildcard; + } + + @Override + public Kind getKind() { + return Kind.IMPORT_FROM; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitImportFrom(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyImportNameTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyImportNameTreeImpl.java new file mode 100644 index 0000000000..b4d4622b20 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyImportNameTreeImpl.java @@ -0,0 +1,59 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyAliasedNameTree; +import org.sonar.python.api.tree.PyImportNameTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyImportNameTreeImpl extends PyTree implements PyImportNameTree { + + private final Token importKeyword; + private final List aliasedNames; + + public PyImportNameTreeImpl(AstNode astNode, Token importKeyword, java.util.List aliasedNames) { + super(astNode); + this.importKeyword = importKeyword; + this.aliasedNames = aliasedNames; + } + + @Override + public Token importKeyword() { + return importKeyword; + } + + @Override + public List modules() { + return aliasedNames; + } + + @Override + public Kind getKind() { + return Kind.IMPORT_NAME; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitImportName(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 149cff6933..cec5a17780 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -29,17 +29,23 @@ import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; import org.sonar.python.api.tree.PyArgListTree; +import org.sonar.python.api.PythonPunctuator; +import org.sonar.python.api.tree.PyAliasedNameTree; import org.sonar.python.api.tree.PyAssertStatementTree; import org.sonar.python.api.tree.PyBreakStatementTree; import org.sonar.python.api.tree.PyClassDefTree; import org.sonar.python.api.tree.PyContinueStatementTree; import org.sonar.python.api.tree.PyDelStatementTree; +import org.sonar.python.api.tree.PyDottedNameTree; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyFunctionDefTree; import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyImportFromTree; +import org.sonar.python.api.tree.PyImportNameTree; +import org.sonar.python.api.tree.PyImportStatementTree; import org.sonar.python.api.tree.PyNameTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; @@ -100,6 +106,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.CLASSDEF)) { return classDefStatement(astNode); } + if (astNode.is(PythonGrammar.IMPORT_STMT)) { + return importStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -221,6 +230,71 @@ public PyBreakStatementTree breakStatement(AstNode astNode) { public PyContinueStatementTree continueStatement(AstNode astNode) { return new PyContinueStatementTreeImpl(astNode, astNode.getToken()); } + + public PyImportStatementTree importStatement(AstNode astNode) { + AstNode importStmt = astNode.getFirstChild(); + if (importStmt.is(PythonGrammar.IMPORT_NAME)) { + return importName(importStmt); + } + return importFromStatement(importStmt); + } + + private PyImportNameTree importName(AstNode astNode) { + Token importKeyword = astNode.getFirstChild(PythonKeyword.IMPORT).getToken(); + List aliasedNames = astNode + .getFirstChild(PythonGrammar.DOTTED_AS_NAMES) + .getChildren(PythonGrammar.DOTTED_AS_NAME).stream() + .map(this::aliasedName) + .collect(Collectors.toList()); + return new PyImportNameTreeImpl(astNode, importKeyword, aliasedNames); + } + + public PyImportFromTree importFromStatement(AstNode astNode) { + Token importKeyword = astNode.getFirstChild(PythonKeyword.IMPORT).getToken(); + Token fromKeyword = astNode.getFirstChild(PythonKeyword.FROM).getToken(); + List dottedPrefixForModule = astNode.getChildren(PythonPunctuator.DOT).stream() + .map(AstNode::getToken) + .collect(Collectors.toList()); + AstNode moduleNode = astNode.getFirstChild(PythonGrammar.DOTTED_NAME); + PyDottedNameTree moduleName = null; + if (moduleNode != null) { + moduleName = dottedName(moduleNode); + } + AstNode importAsnames = astNode.getFirstChild(PythonGrammar.IMPORT_AS_NAMES); + List aliasedImportNames = null; + boolean isWildcardImport = true; + if (importAsnames != null) { + aliasedImportNames = importAsnames.getChildren(PythonGrammar.IMPORT_AS_NAME).stream() + .map(this::aliasedName) + .collect(Collectors.toList()); + isWildcardImport = false; + } + return new PyImportFromTreeImpl(astNode, fromKeyword, dottedPrefixForModule, moduleName, importKeyword, aliasedImportNames, isWildcardImport); + } + + private PyAliasedNameTree aliasedName(AstNode astNode) { + AstNode asKeyword = astNode.getFirstChild(PythonKeyword.AS); + PyDottedNameTree dottedName; + if (astNode.is(PythonGrammar.DOTTED_AS_NAME)) { + dottedName = dottedName(astNode.getFirstChild(PythonGrammar.DOTTED_NAME)); + } else { + // astNode is IMPORT_AS_NAME + AstNode importedName = astNode.getFirstChild(PythonGrammar.NAME); + dottedName = new PyDottedNameTreeImpl(astNode, Collections.singletonList(name(importedName))); + } + if (asKeyword == null) { + return new PyAliasedNameTreeImpl(astNode, null, dottedName, null); + } + return new PyAliasedNameTreeImpl(astNode, asKeyword.getToken(), dottedName, name(astNode.getLastChild(PythonGrammar.NAME))); + } + + private PyDottedNameTree dottedName(AstNode astNode) { + List names = astNode + .getChildren(PythonGrammar.NAME).stream() + .map(this::name) + .collect(Collectors.toList()); + return new PyDottedNameTreeImpl(astNode, names); + } // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 7630e85fe1..49640e89e2 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -52,6 +52,8 @@ public void verify_expected_statement() { testData.put("break", PyBreakStatementTree.class); testData.put("continue", PyContinueStatementTree.class); testData.put("def foo():pass", PyFunctionDefTree.class); + testData.put("import foo", PyImportStatementTree.class); + testData.put("from foo import f", PyImportStatementTree.class); testData.forEach((c,clazz) -> { @@ -317,6 +319,117 @@ public void continueStatement() { assertThat(continueStatement.continueKeyword().getValue()).isEqualTo("continue"); } + @Test + public void importStatement() { + setRootRule(PythonGrammar.IMPORT_STMT); + AstNode astNode = p.parse("import foo"); + PyImportNameTree importStatement = (PyImportNameTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement).isNotNull(); + assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); + assertThat(importStatement.modules()).hasSize(1); + PyAliasedNameTree importedName1 = importStatement.modules().get(0); + assertThat(importedName1.dottedName().names()).hasSize(1); + assertThat(importedName1.dottedName().names().get(0).name()).isEqualTo("foo"); + + astNode = p.parse("import foo as f"); + importStatement = (PyImportNameTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement).isNotNull(); + assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); + assertThat(importStatement.modules()).hasSize(1); + importedName1 = importStatement.modules().get(0); + assertThat(importedName1.dottedName().names()).hasSize(1); + assertThat(importedName1.dottedName().names().get(0).name()).isEqualTo("foo"); + assertThat(importedName1.asKeyword().getValue()).isEqualTo("as"); + assertThat(importedName1.alias().name()).isEqualTo("f"); + + astNode = p.parse("import foo.bar"); + importStatement = (PyImportNameTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement).isNotNull(); + assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); + assertThat(importStatement.modules()).hasSize(1); + importedName1 = importStatement.modules().get(0); + assertThat(importedName1.dottedName().names()).hasSize(2); + assertThat(importedName1.dottedName().names().get(0).name()).isEqualTo("foo"); + assertThat(importedName1.dottedName().names().get(1).name()).isEqualTo("bar"); + + astNode = p.parse("import foo, bar"); + importStatement = (PyImportNameTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement).isNotNull(); + assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); + assertThat(importStatement.modules()).hasSize(2); + importedName1 = importStatement.modules().get(0); + assertThat(importedName1.dottedName().names()).hasSize(1); + assertThat(importedName1.dottedName().names().get(0).name()).isEqualTo("foo"); + PyAliasedNameTree importedName2 = importStatement.modules().get(1); + assertThat(importedName2.dottedName().names()).hasSize(1); + assertThat(importedName2.dottedName().names().get(0).name()).isEqualTo("bar"); + } + + @Test + public void importFromStatement() { + setRootRule(PythonGrammar.IMPORT_STMT); + AstNode astNode = p.parse("from foo import f"); + PyImportFromTree importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement).isNotNull(); + assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); + assertThat(importStatement.dottedPrefixForModule()).isEmpty(); + assertThat(importStatement.fromKeyword().getValue()).isEqualTo("from"); + assertThat(importStatement.module().names().get(0).name()).isEqualTo("foo"); + assertThat(importStatement.isWildcardImport()).isFalse(); + assertThat(importStatement.wildcard()).isNull(); + assertThat(importStatement.importedNames()).hasSize(1); + PyAliasedNameTree aliasedNameTree = importStatement.importedNames().get(0); + assertThat(aliasedNameTree.asKeyword()).isNull(); + assertThat(aliasedNameTree.alias()).isNull(); + assertThat(aliasedNameTree.dottedName().names().get(0).name()).isEqualTo("f"); + + astNode = p.parse("from .foo import f"); + importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement.dottedPrefixForModule()).hasSize(1); + assertThat(importStatement.dottedPrefixForModule().get(0).getValue()).isEqualTo("."); + assertThat(importStatement.module().names().get(0).name()).isEqualTo("foo"); + + astNode = p.parse("from ..foo import f"); + importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement.dottedPrefixForModule()).hasSize(2); + assertThat(importStatement.dottedPrefixForModule().get(0).getValue()).isEqualTo("."); + assertThat(importStatement.dottedPrefixForModule().get(1).getValue()).isEqualTo("."); + assertThat(importStatement.module().names().get(0).name()).isEqualTo("foo"); + + astNode = p.parse("from . import f"); + importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement.dottedPrefixForModule()).hasSize(1); + assertThat(importStatement.dottedPrefixForModule().get(0).getValue()).isEqualTo("."); + assertThat(importStatement.module()).isNull(); + + astNode = p.parse("from foo import f as g"); + importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement.importedNames()).hasSize(1); + aliasedNameTree = importStatement.importedNames().get(0); + assertThat(aliasedNameTree.asKeyword().getValue()).isEqualTo("as"); + assertThat(aliasedNameTree.alias().name()).isEqualTo("g"); + assertThat(aliasedNameTree.dottedName().names().get(0).name()).isEqualTo("f"); + + astNode = p.parse("from foo import f as g, h"); + importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement.importedNames()).hasSize(2); + PyAliasedNameTree aliasedNameTree1 = importStatement.importedNames().get(0); + assertThat(aliasedNameTree1.asKeyword().getValue()).isEqualTo("as"); + assertThat(aliasedNameTree1.alias().name()).isEqualTo("g"); + assertThat(aliasedNameTree1.dottedName().names().get(0).name()).isEqualTo("f"); + + PyAliasedNameTree aliasedNameTree2 = importStatement.importedNames().get(1); + assertThat(aliasedNameTree2.asKeyword()).isNull(); + assertThat(aliasedNameTree2.alias()).isNull(); + assertThat(aliasedNameTree2.dottedName().names().get(0).name()).isEqualTo("h"); + + astNode = p.parse("from foo import *"); + importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + assertThat(importStatement.importedNames()).isNull(); + assertThat(importStatement.isWildcardImport()).isTrue(); + assertThat(importStatement.wildcard().getValue()).isEqualTo("*"); + } + @Test public void funcdef_statement() { setRootRule(PythonGrammar.FUNCDEF); From 7e49566c20cd3a994788f4daaf652aff07c6aae6 Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Wed, 21 Aug 2019 16:17:58 +0200 Subject: [PATCH 22/35] Add for statement --- .../python/api/tree/PyForStatementTree.java | 47 ++++++++ .../sonar/python/api/tree/PyTreeVisitor.java | 2 + .../java/org/sonar/python/api/tree/Tree.java | 2 + .../sonar/python/tree/BaseTreeVisitor.java | 8 ++ .../python/tree/PyForStatementTreeImpl.java | 103 ++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 59 ++++++---- .../python/api/tree/PythonTreeMakerTest.java | 23 ++++ sonar-python-plugin/pom.xml | 2 +- 8 files changed, 225 insertions(+), 21 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyForStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java new file mode 100644 index 0000000000..8b1c92a446 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java @@ -0,0 +1,47 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +public interface PyForStatementTree extends PyStatementTree { + Token forKeyword(); + + List expressions(); + + Token inKeyword(); + + List testExpressions(); + + Token colon(); + + List body(); + + @CheckForNull + Token elseKeyword(); + + @CheckForNull + Token elseColon(); + + @CheckForNull + List elseBody(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index c337928c77..696f43692a 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -62,4 +62,6 @@ public interface PyTreeVisitor { void visitImportFrom(PyImportFromTree pyImportFromTree); void visitImportName(PyImportNameTree pyImportNameTree); + + void visitForStatement(PyForStatementTree pyForStatementTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 8e1dcbf12d..4e5aab7602 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -50,6 +50,8 @@ enum Kind { FILE_INPUT(PyFileInputTree.class), + FOR_STMT(PyForStatementTree.class), + FUNCDEF(PyFunctionDefTree.class), IF_STMT(PyIfStatementTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index c61e64c69a..90047efdcf 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -31,6 +31,7 @@ import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyForStatementTree; import org.sonar.python.api.tree.PyFunctionDefTree; import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyImportFromTree; @@ -178,6 +179,13 @@ public void visitImportFrom(PyImportFromTree pyImportFromTree) { scan(pyImportFromTree.importedNames()); } + public void visitForStatement(PyForStatementTree pyForStatementTree) { + scan(pyForStatementTree.expressions()); + scan(pyForStatementTree.testExpressions()); + scan(pyForStatementTree.body()); + scan(pyForStatementTree.elseBody()); + } + @Override public void visitImportName(PyImportNameTree pyImportNameTree) { scan(pyImportNameTree.modules()); diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyForStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyForStatementTreeImpl.java new file mode 100644 index 0000000000..3519c8d46b --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyForStatementTreeImpl.java @@ -0,0 +1,103 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyForStatementTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyForStatementTreeImpl extends PyTree implements PyForStatementTree { + + private final List expressions; + private final List testExpressions; + private final List body; + private final List elseBody; + + public PyForStatementTreeImpl(AstNode astNode, List expressions, List testExpressions, List body, List elseBody) { + super(astNode); + this.expressions = expressions; + this.testExpressions = testExpressions; + this.body = body; + this.elseBody = elseBody; + } + + @Override + public Kind getKind() { + return Kind.FOR_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitForStatement(this); + } + + @Override + public Token forKeyword() { + return null; + } + + @Override + public List expressions() { + return expressions; + } + + @Override + public Token inKeyword() { + return null; + } + + @Override + public List testExpressions() { + return testExpressions; + } + + @Override + public Token colon() { + return null; + } + + @Override + public List body() { + return body; + } + + @CheckForNull + @Override + public Token elseKeyword() { + return null; + } + + @CheckForNull + @Override + public Token elseColon() { + return null; + } + + @CheckForNull + @Override + public List elseBody() { + return elseBody; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index cec5a17780..c0705a250b 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -41,6 +41,7 @@ import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyForStatementTree; import org.sonar.python.api.tree.PyFunctionDefTree; import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyImportFromTree; @@ -109,6 +110,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.IMPORT_STMT)) { return importStatement(astNode); } + if (astNode.is(PythonGrammar.FOR_STMT)) { + return forStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -145,13 +149,13 @@ private List getStatements(AstNode astNode) { // Simple statements public PyPrintStatementTree printStatement(AstNode astNode) { - List expressions = astNode.getChildren(PythonGrammar.TEST).stream().map(this::expression).collect(Collectors.toList()); + List expressions = expressionsFromTest(astNode); return new PyPrintStatementTreeImpl(astNode, astNode.getTokens().get(0), expressions); } public PyExecStatementTree execStatement(AstNode astNode) { PyExpressionTree expression = expression(astNode.getFirstChild(PythonGrammar.EXPR)); - List expressions = astNode.getChildren(PythonGrammar.TEST).stream().map(this::expression).collect(Collectors.toList()); + List expressions = expressionsFromTest(astNode); if (expressions.isEmpty()) { return new PyExecStatementTreeImpl(astNode, astNode.getTokens().get(0), expression); } @@ -159,7 +163,7 @@ public PyExecStatementTree execStatement(AstNode astNode) { } public PyAssertStatementTree assertStatement(AstNode astNode) { - List expressions = astNode.getChildren(PythonGrammar.TEST).stream().map(this::expression).collect(Collectors.toList()); + List expressions = expressionsFromTest(astNode); return new PyAssertStatementTreeImpl(astNode, astNode.getTokens().get(0), expressions); } @@ -168,10 +172,7 @@ public PyPassStatementTree passStatement(AstNode astNode) { } public PyDelStatementTree delStatement(AstNode astNode) { - AstNode exprListNode = astNode.getFirstChild(PythonGrammar.EXPRLIST); - List expressionTrees = exprListNode.getChildren(PythonGrammar.EXPR, PythonGrammar.STAR_EXPR).stream() - .map(this::expression) - .collect(Collectors.toList()); + List expressionTrees = expressionsFromExprList(astNode.getFirstChild(PythonGrammar.EXPRLIST)); return new PyDelStatementTreeImpl(astNode, astNode.getTokens().get(0), expressionTrees); } @@ -179,9 +180,7 @@ public PyReturnStatementTree returnStatement(AstNode astNode) { AstNode testListNode = astNode.getFirstChild(PythonGrammar.TESTLIST); List expressionTrees = Collections.emptyList(); if (testListNode != null) { - expressionTrees = testListNode.getChildren(PythonGrammar.TEST).stream() - .map(this::expression) - .collect(Collectors.toList()); + expressionTrees = expressionsFromTest(testListNode); } return new PyReturnStatementTreeImpl(astNode, astNode.getTokens().get(0), expressionTrees); } @@ -199,9 +198,7 @@ public PyYieldExpressionTree yieldExpression(AstNode astNode) { } List expressionTrees = Collections.emptyList(); if (nodeContainingExpression != null) { - expressionTrees = nodeContainingExpression.getChildren(PythonGrammar.TEST).stream() - .map(this::expression) - .collect(Collectors.toList()); + expressionTrees = expressionsFromTest(nodeContainingExpression); } return new PyYieldExpressionTreeImpl(astNode, yieldKeyword, fromKeyword == null ? null : fromKeyword.getToken(), expressionTrees); } @@ -330,13 +327,6 @@ private PyElseStatementTree elseStatement(AstNode astNode) { return new PyElseStatementTreeImpl(astNode, elseToken, statements); } - PyExpressionTree expression(AstNode astNode) { - if (astNode.is(PythonGrammar.YIELD_EXPR)) { - return yieldExpression(astNode); - } - return new PyExpressionTreeImpl(astNode); - } - public PyFunctionDefTree funcDefStatement(AstNode astNode) { // TODO decorators PyNameTree name = name(astNode.getFirstChild(PythonGrammar.FUNCNAME).getFirstChild(PythonGrammar.NAME)); @@ -358,4 +348,33 @@ public PyClassDefTree classDefStatement(AstNode astNode) { private PyNameTree name(AstNode astNode) { return new PyNameTreeImpl(astNode, astNode.getFirstChild(GenericTokenType.IDENTIFIER).getTokenOriginalValue()); } + + public PyForStatementTree forStatement(AstNode astNode) { + List expressions = expressionsFromExprList(astNode.getFirstChild(PythonGrammar.EXPRLIST)); + List testExpressions = expressionsFromTest(astNode.getFirstChild(PythonGrammar.TESTLIST)); + AstNode firstSuite = astNode.getFirstChild(PythonGrammar.SUITE); + List body = getStatementsFromSuite(firstSuite); + AstNode lastSuite = astNode.getLastChild(PythonGrammar.SUITE); + List elseBody = lastSuite == firstSuite ? Collections.emptyList() : getStatementsFromSuite(lastSuite); + return new PyForStatementTreeImpl(astNode, expressions, testExpressions, body, elseBody); + } + + // expressions + + private List expressionsFromTest(AstNode astNode) { + return astNode.getChildren(PythonGrammar.TEST).stream().map(this::expression).collect(Collectors.toList()); + } + + private List expressionsFromExprList(AstNode firstChild) { + return firstChild + .getChildren(PythonGrammar.EXPR, PythonGrammar.STAR_EXPR) + .stream().map(this::expression).collect(Collectors.toList()); + } + + PyExpressionTree expression(AstNode astNode) { + if (astNode.is(PythonGrammar.YIELD_EXPR)) { + return yieldExpression(astNode); + } + return new PyExpressionTreeImpl(astNode); + } } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 49640e89e2..ed8642e8ba 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -54,6 +54,8 @@ public void verify_expected_statement() { testData.put("def foo():pass", PyFunctionDefTree.class); testData.put("import foo", PyImportStatementTree.class); testData.put("from foo import f", PyImportStatementTree.class); + testData.put("class toto:pass", PyClassDefTree.class); + testData.put("for foo in bar:pass", PyForStatementTree.class); testData.forEach((c,clazz) -> { @@ -456,4 +458,25 @@ public void classdef_statement() { assertThat(classDefTree.args()).isNull(); assertThat(classDefTree.decorators()).isNull(); } + + @Test + public void for_statement() { + setRootRule(PythonGrammar.FOR_STMT); + AstNode astNode = p.parse("for foo in bar: pass"); + PyForStatementTree pyForStatementTree = new PythonTreeMaker().forStatement(astNode); + assertThat(pyForStatementTree.expressions()).hasSize(1); + assertThat(pyForStatementTree.testExpressions()).hasSize(1); + assertThat(pyForStatementTree.body()).hasSize(1); + assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(pyForStatementTree.elseBody()).isEmpty(); + + astNode = p.parse("for foo in bar:\n pass\nelse:\n pass"); + pyForStatementTree = new PythonTreeMaker().forStatement(astNode); + assertThat(pyForStatementTree.expressions()).hasSize(1); + assertThat(pyForStatementTree.testExpressions()).hasSize(1); + assertThat(pyForStatementTree.body()).hasSize(1); + assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(pyForStatementTree.elseBody()).hasSize(1); + assertThat(pyForStatementTree.elseBody().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + } } diff --git a/sonar-python-plugin/pom.xml b/sonar-python-plugin/pom.xml index 05d2b2689c..5c94a0a1cb 100644 --- a/sonar-python-plugin/pom.xml +++ b/sonar-python-plugin/pom.xml @@ -135,7 +135,7 @@ - 2800000 + 2900000 2600000 ${project.build.directory}/${project.build.finalName}.jar From 72cb459239c75fa788efa7d595c341121fd89ad6 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Wed, 21 Aug 2019 16:33:13 +0200 Subject: [PATCH 23/35] Add PyGlobalStatement and PyNonlocalStatement --- .../api/tree/PyGlobalStatementTree.java | 29 ++++++++++ .../api/tree/PyNonlocalStatementTree.java | 29 ++++++++++ .../sonar/python/api/tree/PyTreeVisitor.java | 7 ++- .../java/org/sonar/python/api/tree/Tree.java | 4 ++ .../sonar/python/tree/BaseTreeVisitor.java | 12 ++++ .../tree/PyGlobalStatementTreeImpl.java | 58 +++++++++++++++++++ .../tree/PyNonlocalStatementTreeImpl.java | 58 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 23 +++++++- .../python/api/tree/PythonTreeMakerTest.java | 35 +++++++++++ 9 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyGlobalStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyNonlocalStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyGlobalStatementTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyNonlocalStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyGlobalStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyGlobalStatementTree.java new file mode 100644 index 0000000000..dd12977f8b --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyGlobalStatementTree.java @@ -0,0 +1,29 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyGlobalStatementTree extends PyStatementTree { + Token globalKeyword(); + + List variables(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyNonlocalStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyNonlocalStatementTree.java new file mode 100644 index 0000000000..fa99a13f6d --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyNonlocalStatementTree.java @@ -0,0 +1,29 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyNonlocalStatementTree extends PyStatementTree { + Token nonlocalKeyword(); + + List variables(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 696f43692a..53f8bfa4fd 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -64,4 +64,9 @@ public interface PyTreeVisitor { void visitImportName(PyImportNameTree pyImportNameTree); void visitForStatement(PyForStatementTree pyForStatementTree); -} + + void visitGlobalStatement(PyGlobalStatementTree pyGlobalStatementTree); + + void visitNonlocalStatement(PyNonlocalStatementTree pyNonlocalStatementTree); + +} \ No newline at end of file diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 4e5aab7602..e082477b20 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -54,6 +54,8 @@ enum Kind { FUNCDEF(PyFunctionDefTree.class), + GLOBAL_STMT(PyGlobalStatementTree.class), + IF_STMT(PyIfStatementTree.class), IMPORT_FROM(PyImportFromTree.class), @@ -64,6 +66,8 @@ enum Kind { NAME(PyNameTree.class), + NONLOCAL_STMT(PyNonlocalStatementTree.class), + PASS_STMT(PyPassStatementTree.class), PRINT_STMT(PyPrintStatementTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 90047efdcf..7292dd88fc 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -33,10 +33,12 @@ import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyForStatementTree; import org.sonar.python.api.tree.PyFunctionDefTree; +import org.sonar.python.api.tree.PyGlobalStatementTree; import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyImportFromTree; import org.sonar.python.api.tree.PyImportNameTree; import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyNonlocalStatementTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; import org.sonar.python.api.tree.PyRaiseStatementTree; @@ -191,4 +193,14 @@ public void visitImportName(PyImportNameTree pyImportNameTree) { scan(pyImportNameTree.modules()); } + @Override + public void visitGlobalStatement(PyGlobalStatementTree pyGlobalStatementTree) { + scan(pyGlobalStatementTree.variables()); + } + + @Override + public void visitNonlocalStatement(PyNonlocalStatementTree pyNonlocalStatementTree) { + scan(pyNonlocalStatementTree.variables()); + } + } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyGlobalStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyGlobalStatementTreeImpl.java new file mode 100644 index 0000000000..58b1004326 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyGlobalStatementTreeImpl.java @@ -0,0 +1,58 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyGlobalStatementTree; +import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyGlobalStatementTreeImpl extends PyTree implements PyGlobalStatementTree { + private final Token globalKeyword; + private final List variables; + + public PyGlobalStatementTreeImpl(AstNode astNode, Token globalKeyword, List variables) { + super(astNode); + this.globalKeyword = globalKeyword; + this.variables = variables; + } + + @Override + public Token globalKeyword() { + return globalKeyword; + } + + @Override + public List variables() { + return variables; + } + + @Override + public Kind getKind() { + return Kind.GLOBAL_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitGlobalStatement(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyNonlocalStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyNonlocalStatementTreeImpl.java new file mode 100644 index 0000000000..5e1e56153a --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyNonlocalStatementTreeImpl.java @@ -0,0 +1,58 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyNonlocalStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyNonlocalStatementTreeImpl extends PyTree implements PyNonlocalStatementTree { + private final Token nonlocalKeyword; + private final List variables; + + public PyNonlocalStatementTreeImpl(AstNode astNode, Token nonlocalKeyword, List variables) { + super(astNode); + this.nonlocalKeyword = nonlocalKeyword; + this.variables = variables; + } + + @Override + public Token nonlocalKeyword() { + return nonlocalKeyword; + } + + @Override + public List variables() { + return variables; + } + + @Override + public Kind getKind() { + return Kind.NONLOCAL_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitNonlocalStatement(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index c0705a250b..bf42d57279 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -28,9 +28,9 @@ import java.util.stream.Collectors; import org.sonar.python.api.PythonGrammar; import org.sonar.python.api.PythonKeyword; -import org.sonar.python.api.tree.PyArgListTree; import org.sonar.python.api.PythonPunctuator; import org.sonar.python.api.tree.PyAliasedNameTree; +import org.sonar.python.api.tree.PyArgListTree; import org.sonar.python.api.tree.PyAssertStatementTree; import org.sonar.python.api.tree.PyBreakStatementTree; import org.sonar.python.api.tree.PyClassDefTree; @@ -43,11 +43,13 @@ import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyForStatementTree; import org.sonar.python.api.tree.PyFunctionDefTree; +import org.sonar.python.api.tree.PyGlobalStatementTree; import org.sonar.python.api.tree.PyIfStatementTree; import org.sonar.python.api.tree.PyImportFromTree; import org.sonar.python.api.tree.PyImportNameTree; import org.sonar.python.api.tree.PyImportStatementTree; import org.sonar.python.api.tree.PyNameTree; +import org.sonar.python.api.tree.PyNonlocalStatementTree; import org.sonar.python.api.tree.PyPassStatementTree; import org.sonar.python.api.tree.PyPrintStatementTree; import org.sonar.python.api.tree.PyRaiseStatementTree; @@ -113,6 +115,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.FOR_STMT)) { return forStatement(astNode); } + if (astNode.is(PythonGrammar.GLOBAL_STMT)) { + return globalStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -292,6 +297,22 @@ private PyDottedNameTree dottedName(AstNode astNode) { .collect(Collectors.toList()); return new PyDottedNameTreeImpl(astNode, names); } + + public PyGlobalStatementTree globalStatement(AstNode astNode) { + Token globalKeyword = astNode.getFirstChild(PythonKeyword.GLOBAL).getToken(); + List variables = astNode.getChildren(PythonGrammar.NAME).stream() + .map(this::name) + .collect(Collectors.toList()); + return new PyGlobalStatementTreeImpl(astNode, globalKeyword, variables); + } + + public PyNonlocalStatementTree nonlocalStatement(AstNode astNode) { + Token nonlocalKeyword = astNode.getFirstChild(PythonKeyword.NONLOCAL).getToken(); + List variables = astNode.getChildren(PythonGrammar.NAME).stream() + .map(this::name) + .collect(Collectors.toList()); + return new PyNonlocalStatementTreeImpl(astNode, nonlocalKeyword, variables); + } // Compound statements public PyIfStatementTree ifStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index ed8642e8ba..f4f71c82e5 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -56,6 +56,7 @@ public void verify_expected_statement() { testData.put("from foo import f", PyImportStatementTree.class); testData.put("class toto:pass", PyClassDefTree.class); testData.put("for foo in bar:pass", PyForStatementTree.class); + testData.put("global foo", PyGlobalStatementTree.class); testData.forEach((c,clazz) -> { @@ -432,6 +433,40 @@ public void importFromStatement() { assertThat(importStatement.wildcard().getValue()).isEqualTo("*"); } + @Test + public void globalStatement() { + setRootRule(PythonGrammar.GLOBAL_STMT); + AstNode astNode = p.parse("global foo"); + PyGlobalStatementTree globalStatement = new PythonTreeMaker().globalStatement(astNode); + assertThat(globalStatement.globalKeyword().getValue()).isEqualTo("global"); + assertThat(globalStatement.variables()).hasSize(1); + assertThat(globalStatement.variables().get(0).name()).isEqualTo("foo"); + + astNode = p.parse("global foo, bar"); + globalStatement = new PythonTreeMaker().globalStatement(astNode); + assertThat(globalStatement.globalKeyword().getValue()).isEqualTo("global"); + assertThat(globalStatement.variables()).hasSize(2); + assertThat(globalStatement.variables().get(0).name()).isEqualTo("foo"); + assertThat(globalStatement.variables().get(1).name()).isEqualTo("bar"); + } + + @Test + public void nonlocalStatement() { + setRootRule(PythonGrammar.NONLOCAL_STMT); + AstNode astNode = p.parse("nonlocal foo"); + PyNonlocalStatementTree nonlocalStatement = new PythonTreeMaker().nonlocalStatement(astNode); + assertThat(nonlocalStatement.nonlocalKeyword().getValue()).isEqualTo("nonlocal"); + assertThat(nonlocalStatement.variables()).hasSize(1); + assertThat(nonlocalStatement.variables().get(0).name()).isEqualTo("foo"); + + astNode = p.parse("nonlocal foo, bar"); + nonlocalStatement = new PythonTreeMaker().nonlocalStatement(astNode); + assertThat(nonlocalStatement.nonlocalKeyword().getValue()).isEqualTo("nonlocal"); + assertThat(nonlocalStatement.variables()).hasSize(2); + assertThat(nonlocalStatement.variables().get(0).name()).isEqualTo("foo"); + assertThat(nonlocalStatement.variables().get(1).name()).isEqualTo("bar"); + } + @Test public void funcdef_statement() { setRootRule(PythonGrammar.FUNCDEF); From fc99312752894f1aa42395fbe5c0d9a307d1e107 Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Wed, 21 Aug 2019 16:49:54 +0200 Subject: [PATCH 24/35] Add while statement --- .../python/api/tree/PyForStatementTree.java | 1 - .../sonar/python/api/tree/PyTreeVisitor.java | 3 +- .../python/api/tree/PyWhileStatementTree.java | 42 +++++++++ .../java/org/sonar/python/api/tree/Tree.java | 2 + .../sonar/python/tree/BaseTreeVisitor.java | 9 ++ .../python/tree/PyWhileStatementTreeImpl.java | 91 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 12 +++ .../python/api/tree/PythonTreeMakerTest.java | 21 +++++ 8 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyWhileStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyWhileStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java index 8b1c92a446..e4b98687e3 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java @@ -42,6 +42,5 @@ public interface PyForStatementTree extends PyStatementTree { @CheckForNull Token elseColon(); - @CheckForNull List elseBody(); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 53f8bfa4fd..38662b7a90 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -69,4 +69,5 @@ public interface PyTreeVisitor { void visitNonlocalStatement(PyNonlocalStatementTree pyNonlocalStatementTree); -} \ No newline at end of file + void visitWhileStatement(PyWhileStatementTree pyWhileStatementTree); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyWhileStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyWhileStatementTree.java new file mode 100644 index 0000000000..a3477c8fe0 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyWhileStatementTree.java @@ -0,0 +1,42 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +public interface PyWhileStatementTree extends PyStatementTree { + Token whileKeyword(); + + PyExpressionTree condition(); + + Token colon(); + + List body(); + + @CheckForNull + Token elseKeyword(); + + @CheckForNull + Token elseColon(); + + List elseBody(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index e082477b20..e5dc3574cc 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -76,6 +76,8 @@ enum Kind { RETURN_STMT(PyReturnStatementTree.class), + WHILE_STMT(PyWhileStatementTree.class), + YIELD_EXPR(PyYieldExpressionTree.class), YIELD_STMT(PyYieldStatementTree.class); diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 7292dd88fc..8b537a3e2d 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -44,6 +44,7 @@ import org.sonar.python.api.tree.PyRaiseStatementTree; import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.PyWhileStatementTree; import org.sonar.python.api.tree.PyYieldExpressionTree; import org.sonar.python.api.tree.PyYieldStatementTree; import org.sonar.python.api.tree.Tree; @@ -181,6 +182,7 @@ public void visitImportFrom(PyImportFromTree pyImportFromTree) { scan(pyImportFromTree.importedNames()); } + @Override public void visitForStatement(PyForStatementTree pyForStatementTree) { scan(pyForStatementTree.expressions()); scan(pyForStatementTree.testExpressions()); @@ -203,4 +205,11 @@ public void visitNonlocalStatement(PyNonlocalStatementTree pyNonlocalStatementTr scan(pyNonlocalStatementTree.variables()); } + @Override + public void visitWhileStatement(PyWhileStatementTree pyWhileStatementTree) { + scan(pyWhileStatementTree.condition()); + scan(pyWhileStatementTree.body()); + scan(pyWhileStatementTree.elseBody()); + } + } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyWhileStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyWhileStatementTreeImpl.java new file mode 100644 index 0000000000..56617cb862 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyWhileStatementTreeImpl.java @@ -0,0 +1,91 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.PyWhileStatementTree; + +public class PyWhileStatementTreeImpl extends PyTree implements PyWhileStatementTree { + + private final PyExpressionTree condition; + private final List body; + private final List elseBody; + + public PyWhileStatementTreeImpl(AstNode astNode, PyExpressionTree condition, List body, List elseBody) { + super(astNode); + this.condition = condition; + this.body = body; + this.elseBody = elseBody; + } + + @Override + public Kind getKind() { + return Kind.WHILE_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitWhileStatement(this); + } + + @Override + public Token whileKeyword() { + return null; + } + + @Override + public PyExpressionTree condition() { + return condition; + } + + @Override + public Token colon() { + return null; + } + + @Override + public List body() { + return body; + } + + @CheckForNull + @Override + public Token elseKeyword() { + return null; + } + + @CheckForNull + @Override + public Token elseColon() { + return null; + } + + @CheckForNull + @Override + public List elseBody() { + return elseBody; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index bf42d57279..35acf3efc9 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -115,6 +115,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.FOR_STMT)) { return forStatement(astNode); } + if (astNode.is(PythonGrammar.WHILE_STMT)) { + return whileStatement(astNode); + } if (astNode.is(PythonGrammar.GLOBAL_STMT)) { return globalStatement(astNode); } @@ -380,6 +383,15 @@ public PyForStatementTree forStatement(AstNode astNode) { return new PyForStatementTreeImpl(astNode, expressions, testExpressions, body, elseBody); } + public PyWhileStatementTreeImpl whileStatement(AstNode astNode) { + PyExpressionTree condition = expression(astNode.getFirstChild(PythonGrammar.TEST)); + AstNode firstSuite = astNode.getFirstChild(PythonGrammar.SUITE); + List body = getStatementsFromSuite(firstSuite); + AstNode lastSuite = astNode.getLastChild(PythonGrammar.SUITE); + List elseBody = lastSuite == firstSuite ? Collections.emptyList() : getStatementsFromSuite(lastSuite); + return new PyWhileStatementTreeImpl(astNode, condition, body, elseBody); + } + // expressions private List expressionsFromTest(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index f4f71c82e5..11b28c23e5 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.sonar.python.api.PythonGrammar; import org.sonar.python.parser.RuleTest; +import org.sonar.python.tree.PyWhileStatementTreeImpl; import org.sonar.python.tree.PythonTreeMaker; import static org.assertj.core.api.Assertions.assertThat; @@ -57,6 +58,7 @@ public void verify_expected_statement() { testData.put("class toto:pass", PyClassDefTree.class); testData.put("for foo in bar:pass", PyForStatementTree.class); testData.put("global foo", PyGlobalStatementTree.class); + testData.put("while cond: pass", PyWhileStatementTree.class); testData.forEach((c,clazz) -> { @@ -514,4 +516,23 @@ public void for_statement() { assertThat(pyForStatementTree.elseBody()).hasSize(1); assertThat(pyForStatementTree.elseBody().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); } + + @Test + public void while_statement() { + setRootRule(PythonGrammar.WHILE_STMT); + AstNode astNode = p.parse("while foo : pass"); + PyWhileStatementTreeImpl pyForStatementTree = new PythonTreeMaker().whileStatement(astNode); + assertThat(pyForStatementTree.condition()).isNotNull(); + assertThat(pyForStatementTree.body()).hasSize(1); + assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(pyForStatementTree.elseBody()).isEmpty(); + + astNode = p.parse("while foo:\n pass\nelse:\n pass"); + pyForStatementTree = new PythonTreeMaker().whileStatement(astNode); + assertThat(pyForStatementTree.condition()).isNotNull(); + assertThat(pyForStatementTree.body()).hasSize(1); + assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(pyForStatementTree.elseBody()).hasSize(1); + assertThat(pyForStatementTree.elseBody().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + } } From eb612ce2cd3e1450ba510e6421945e5527cc65ee Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Wed, 21 Aug 2019 17:11:30 +0200 Subject: [PATCH 25/35] add PyExpressionStatement, add test for PyNonlocalStatement --- .../api/tree/PyExpressionStatementTree.java | 26 ++++++++++ .../tree/PyExpressionStatementTreeImpl.java | 49 +++++++++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 15 ++++++ .../python/api/tree/PythonTreeMakerTest.java | 15 +++++- 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyExpressionStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionStatementTree.java new file mode 100644 index 0000000000..8327c2252f --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyExpressionStatementTree.java @@ -0,0 +1,26 @@ +/* + * 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.api.tree; + +import java.util.List; + +public interface PyExpressionStatementTree extends PyStatementTree { + List expressions(); +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionStatementTreeImpl.java new file mode 100644 index 0000000000..812ba59b77 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionStatementTreeImpl.java @@ -0,0 +1,49 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import java.util.List; +import org.sonar.python.api.tree.PyExpressionStatementTree; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyExpressionStatementTreeImpl extends PyTree implements PyExpressionStatementTree { + private final List expressions; + + public PyExpressionStatementTreeImpl(AstNode astNode, List expressions) { + super(astNode); + this.expressions = expressions; + } + @Override + public Kind getKind() { + return null; + } + + @Override + public void accept(PyTreeVisitor visitor) { + + } + + @Override + public List expressions() { + return expressions; + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 35acf3efc9..e8eb19dc83 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -39,6 +39,7 @@ import org.sonar.python.api.tree.PyDottedNameTree; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; +import org.sonar.python.api.tree.PyExpressionStatementTree; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyForStatementTree; @@ -121,6 +122,12 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.GLOBAL_STMT)) { return globalStatement(astNode); } + if (astNode.is(PythonGrammar.NONLOCAL_STMT)) { + return nonlocalStatement(astNode); + } + if (astNode.is(PythonGrammar.EXPRESSION_STMT)) { + return expressionStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -392,6 +399,14 @@ public PyWhileStatementTreeImpl whileStatement(AstNode astNode) { return new PyWhileStatementTreeImpl(astNode, condition, body, elseBody); } + public PyExpressionStatementTree expressionStatement(AstNode astNode) { + // TODO: handle ANNASSIGN, b.sequence(AUGASSIGN, b.firstOf(YIELD_EXPR, TESTLIST)), b.zeroOrMore("=", b.firstOf(YIELD_EXPR, TESTLIST_STAR_EXPR) + List expressions = astNode.getFirstChild(PythonGrammar.TESTLIST_STAR_EXPR).getChildren(PythonGrammar.TEST, PythonGrammar.STAR_EXPR).stream() + .map(this::expression) + .collect(Collectors.toList()); + return new PyExpressionStatementTreeImpl(astNode, expressions); + } + // expressions private List expressionsFromTest(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 11b28c23e5..a1330b401e 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -58,8 +58,9 @@ public void verify_expected_statement() { testData.put("class toto:pass", PyClassDefTree.class); testData.put("for foo in bar:pass", PyForStatementTree.class); testData.put("global foo", PyGlobalStatementTree.class); + testData.put("nonlocal foo", PyNonlocalStatementTree.class); testData.put("while cond: pass", PyWhileStatementTree.class); - + testData.put("'foo'", PyExpressionStatementTree.class); testData.forEach((c,clazz) -> { AstNode astNode = p.parse(c); @@ -535,4 +536,16 @@ public void while_statement() { assertThat(pyForStatementTree.elseBody()).hasSize(1); assertThat(pyForStatementTree.elseBody().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); } + + @Test + public void expression_statement() { + setRootRule(PythonGrammar.EXPRESSION_STMT); + AstNode astNode = p.parse("'foo'"); + PyExpressionStatementTree expressionStatement = new PythonTreeMaker().expressionStatement(astNode); + assertThat(expressionStatement.expressions()).hasSize(1); + + astNode = p.parse("'foo', 'bar'"); + expressionStatement = new PythonTreeMaker().expressionStatement(astNode); + assertThat(expressionStatement.expressions()).hasSize(2); + } } From d0c7442ba82fddef3a9c21d1d551fa62abeb3236 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Wed, 21 Aug 2019 17:24:43 +0200 Subject: [PATCH 26/35] ExpressionStatement: implement accept and getKind --- .../main/java/org/sonar/python/api/tree/PyTreeVisitor.java | 2 ++ .../src/main/java/org/sonar/python/api/tree/Tree.java | 2 ++ .../main/java/org/sonar/python/tree/BaseTreeVisitor.java | 6 ++++++ .../sonar/python/tree/PyExpressionStatementTreeImpl.java | 4 ++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 38662b7a90..9995f7af7f 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -70,4 +70,6 @@ public interface PyTreeVisitor { void visitNonlocalStatement(PyNonlocalStatementTree pyNonlocalStatementTree); void visitWhileStatement(PyWhileStatementTree pyWhileStatementTree); + + void visitExpressionStatement(PyExpressionStatementTree pyExpressionStatementTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index e5dc3574cc..ca0544c312 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -48,6 +48,8 @@ enum Kind { EXEC_STMT(PyExecStatementTree.class), + EXPRESSION_STMT(PyExpressionStatementTree.class), + FILE_INPUT(PyFileInputTree.class), FOR_STMT(PyForStatementTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 8b537a3e2d..44d41b2771 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -30,6 +30,7 @@ import org.sonar.python.api.tree.PyDottedNameTree; import org.sonar.python.api.tree.PyElseStatementTree; import org.sonar.python.api.tree.PyExecStatementTree; +import org.sonar.python.api.tree.PyExpressionStatementTree; import org.sonar.python.api.tree.PyFileInputTree; import org.sonar.python.api.tree.PyForStatementTree; import org.sonar.python.api.tree.PyFunctionDefTree; @@ -212,4 +213,9 @@ public void visitWhileStatement(PyWhileStatementTree pyWhileStatementTree) { scan(pyWhileStatementTree.elseBody()); } + @Override + public void visitExpressionStatement(PyExpressionStatementTree pyExpressionStatementTree) { + scan(pyExpressionStatementTree.expressions()); + } + } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionStatementTreeImpl.java index 812ba59b77..c4789be376 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyExpressionStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExpressionStatementTreeImpl.java @@ -34,12 +34,12 @@ public PyExpressionStatementTreeImpl(AstNode astNode, List exp } @Override public Kind getKind() { - return null; + return Kind.EXPRESSION_STMT; } @Override public void accept(PyTreeVisitor visitor) { - + visitor.visitExpressionStatement(this); } @Override From cb7ea52c25ae498b05f9c47575a19364936f9f38 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Wed, 21 Aug 2019 19:04:40 +0200 Subject: [PATCH 27/35] Add PyTryStatement --- .../python/api/tree/PyExceptClauseTree.java | 42 +++++++ .../python/api/tree/PyFinallyClauseTree.java | 29 +++++ .../sonar/python/api/tree/PyTreeVisitor.java | 6 + .../python/api/tree/PyTryStatementTree.java | 38 ++++++ .../java/org/sonar/python/api/tree/Tree.java | 6 + .../sonar/python/tree/BaseTreeVisitor.java | 22 ++++ .../python/tree/PyExceptClauseTreeImpl.java | 112 ++++++++++++++++++ .../python/tree/PyFinallyClauseTreeImpl.java | 58 +++++++++ .../python/tree/PyTryStatementTreeImpl.java | 85 +++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 45 +++++++ .../python/api/tree/PythonTreeMakerTest.java | 57 +++++++++ 11 files changed, 500 insertions(+) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyExceptClauseTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyFinallyClauseTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyTryStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyExceptClauseTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyFinallyClauseTreeImpl.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyTryStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyExceptClauseTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyExceptClauseTree.java new file mode 100644 index 0000000000..50ff4b53be --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyExceptClauseTree.java @@ -0,0 +1,42 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +public interface PyExceptClauseTree extends Tree { + Token exceptKeyword(); + + List body(); + + @CheckForNull + Token asKeyword(); + + @CheckForNull + Token commaToken(); + + @CheckForNull + PyExpressionTree exception(); + + @CheckForNull + PyExpressionTree exceptionInstance(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyFinallyClauseTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyFinallyClauseTree.java new file mode 100644 index 0000000000..3d24236d4e --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyFinallyClauseTree.java @@ -0,0 +1,29 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyFinallyClauseTree extends Tree { + Token finallyKeyword(); + + List body(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 9995f7af7f..30e4a66b2e 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -72,4 +72,10 @@ public interface PyTreeVisitor { void visitWhileStatement(PyWhileStatementTree pyWhileStatementTree); void visitExpressionStatement(PyExpressionStatementTree pyExpressionStatementTree); + + void visitTryStatement(PyTryStatementTree pyTryStatementTree); + + void visitFinallyClause(PyFinallyClauseTree pyFinallyClauseTree); + + void visitExceptClause(PyExceptClauseTree pyExceptClauseTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTryStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTryStatementTree.java new file mode 100644 index 0000000000..891174472e --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTryStatementTree.java @@ -0,0 +1,38 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; + +public interface PyTryStatementTree extends PyStatementTree { + Token tryKeyword(); + + List exceptClauses(); + + @CheckForNull + PyFinallyClauseTree finallyClause(); + + @CheckForNull + PyElseStatementTree elseClause(); + + List body(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index ca0544c312..37970d86b0 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -46,12 +46,16 @@ enum Kind { ELSE_STMT(PyElseStatementTree.class), + EXCEPT_CLAUSE(PyExceptClauseTree.class), + EXEC_STMT(PyExecStatementTree.class), EXPRESSION_STMT(PyExpressionStatementTree.class), FILE_INPUT(PyFileInputTree.class), + FINALLY_CLAUSE(PyFinallyClauseTree.class), + FOR_STMT(PyForStatementTree.class), FUNCDEF(PyFunctionDefTree.class), @@ -78,6 +82,8 @@ enum Kind { RETURN_STMT(PyReturnStatementTree.class), + TRY_STMT(PyTryStatementTree.class), + WHILE_STMT(PyWhileStatementTree.class), YIELD_EXPR(PyYieldExpressionTree.class), diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 44d41b2771..5b30d51f02 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -29,9 +29,11 @@ import org.sonar.python.api.tree.PyDelStatementTree; import org.sonar.python.api.tree.PyDottedNameTree; import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyExceptClauseTree; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionStatementTree; import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyFinallyClauseTree; import org.sonar.python.api.tree.PyForStatementTree; import org.sonar.python.api.tree.PyFunctionDefTree; import org.sonar.python.api.tree.PyGlobalStatementTree; @@ -45,6 +47,7 @@ import org.sonar.python.api.tree.PyRaiseStatementTree; import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.PyTryStatementTree; import org.sonar.python.api.tree.PyWhileStatementTree; import org.sonar.python.api.tree.PyYieldExpressionTree; import org.sonar.python.api.tree.PyYieldStatementTree; @@ -218,4 +221,23 @@ public void visitExpressionStatement(PyExpressionStatementTree pyExpressionState scan(pyExpressionStatementTree.expressions()); } + @Override + public void visitTryStatement(PyTryStatementTree pyTryStatementTree) { + scan(pyTryStatementTree.body()); + scan(pyTryStatementTree.exceptClauses()); + scan(pyTryStatementTree.finallyClause()); + scan(pyTryStatementTree.elseClause()); + } + + @Override + public void visitFinallyClause(PyFinallyClauseTree pyFinallyClauseTree) { + scan(pyFinallyClauseTree.body()); + } + + @Override + public void visitExceptClause(PyExceptClauseTree pyExceptClauseTree) { + scan(pyExceptClauseTree.exception()); + scan(pyExceptClauseTree.exceptionInstance()); + scan(pyExceptClauseTree.body()); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyExceptClauseTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyExceptClauseTreeImpl.java new file mode 100644 index 0000000000..d06d44beb0 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyExceptClauseTreeImpl.java @@ -0,0 +1,112 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyExceptClauseTree; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyExceptClauseTreeImpl extends PyTree implements PyExceptClauseTree { + private final Token exceptKeyword; + private final List body; + private final PyExpressionTree exception; + private final Token asKeyword; + private final Token commaToken; + private final PyExpressionTree exceptionInstance; + + public PyExceptClauseTreeImpl(AstNode astNode, Token exceptKeyword, List body) { + super(astNode); + this.exceptKeyword = exceptKeyword; + this.body = body; + this.exception = null; + this.asKeyword = null; + this.commaToken = null; + this.exceptionInstance = null; + } + + public PyExceptClauseTreeImpl(AstNode astNode, Token exceptKeyword, List body, PyExpressionTree exception, AstNode asNode, AstNode commaNode, PyExpressionTree exceptionInstance) { + super(astNode); + this.exceptKeyword = exceptKeyword; + this.body = body; + this.exception = exception; + this.asKeyword = asNode != null ? asNode.getToken() : null; + this.commaToken = commaNode != null ? commaNode.getToken() : null; + this.exceptionInstance = exceptionInstance; + } + + public PyExceptClauseTreeImpl(AstNode except, Token exceptKeyword, List body, PyExpressionTree exception) { + super(except); + this.exceptKeyword = exceptKeyword; + this.body = body; + this.exception = exception; + this.asKeyword = null; + this.commaToken = null; + this.exceptionInstance = null; + } + + @Override + public Token exceptKeyword() { + return exceptKeyword; + } + + @Override + public List body() { + return body; + } + + @CheckForNull + @Override + public Token asKeyword() { + return asKeyword; + } + + @CheckForNull + @Override + public Token commaToken() { + return commaToken; + } + + @CheckForNull + @Override + public PyExpressionTree exception() { + return exception; + } + + @CheckForNull + @Override + public PyExpressionTree exceptionInstance() { + return exceptionInstance; + } + + @Override + public Kind getKind() { + return Kind.EXCEPT_CLAUSE; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitExceptClause(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyFinallyClauseTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyFinallyClauseTreeImpl.java new file mode 100644 index 0000000000..33fa2c585f --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyFinallyClauseTreeImpl.java @@ -0,0 +1,58 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import org.sonar.python.api.tree.PyFinallyClauseTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; + +public class PyFinallyClauseTreeImpl extends PyTree implements PyFinallyClauseTree { + private final Token finallyKeyword; + private final List body; + + public PyFinallyClauseTreeImpl(AstNode astNode, Token finallyKeyword, List body) { + super(astNode); + this.finallyKeyword = finallyKeyword; + this.body = body; + } + + @Override + public Token finallyKeyword() { + return finallyKeyword; + } + + @Override + public List body() { + return body; + } + + @Override + public Kind getKind() { + return Kind.FINALLY_CLAUSE; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitFinallyClause(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyTryStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyTryStatementTreeImpl.java new file mode 100644 index 0000000000..b11c8dea76 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyTryStatementTreeImpl.java @@ -0,0 +1,85 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyExceptClauseTree; +import org.sonar.python.api.tree.PyFinallyClauseTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.PyTryStatementTree; + +public class PyTryStatementTreeImpl extends PyTree implements PyTryStatementTree { + private final Token tryKeyword; + private final List tryBody; + private final List exceptClauses; + private final PyFinallyClauseTree finallyClause; + private final PyElseStatementTree elseStatement; + + public PyTryStatementTreeImpl(AstNode astNode, Token tryKeyword, List tryBody, List exceptClauses, PyFinallyClauseTree finallyClause, PyElseStatementTree elseStatement) { + super(astNode); + this.tryKeyword = tryKeyword; + this.tryBody = tryBody; + this.exceptClauses = exceptClauses; + this.finallyClause = finallyClause; + this.elseStatement = elseStatement; + } + + @Override + public Token tryKeyword() { + return tryKeyword; + } + + @Override + public List exceptClauses() { + return exceptClauses; + } + + @CheckForNull + @Override + public PyFinallyClauseTree finallyClause() { + return finallyClause; + } + + @CheckForNull + @Override + public PyElseStatementTree elseClause() { + return elseStatement; + } + + @Override + public List body() { + return tryBody; + } + + @Override + public Kind getKind() { + return Kind.TRY_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitTryStatement(this); + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index e8eb19dc83..423aa546a6 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -38,10 +38,12 @@ import org.sonar.python.api.tree.PyDelStatementTree; import org.sonar.python.api.tree.PyDottedNameTree; import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyExceptClauseTree; import org.sonar.python.api.tree.PyExecStatementTree; import org.sonar.python.api.tree.PyExpressionStatementTree; import org.sonar.python.api.tree.PyExpressionTree; import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyFinallyClauseTree; import org.sonar.python.api.tree.PyForStatementTree; import org.sonar.python.api.tree.PyFunctionDefTree; import org.sonar.python.api.tree.PyGlobalStatementTree; @@ -56,6 +58,7 @@ import org.sonar.python.api.tree.PyRaiseStatementTree; import org.sonar.python.api.tree.PyReturnStatementTree; import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTryStatementTree; import org.sonar.python.api.tree.PyTypedArgListTree; import org.sonar.python.api.tree.PyYieldExpressionTree; import org.sonar.python.api.tree.PyYieldStatementTree; @@ -128,6 +131,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.EXPRESSION_STMT)) { return expressionStatement(astNode); } + if (astNode.is(PythonGrammar.TRY_STMT)) { + return tryStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -407,6 +413,45 @@ public PyExpressionStatementTree expressionStatement(AstNode astNode) { return new PyExpressionStatementTreeImpl(astNode, expressions); } + public PyTryStatementTree tryStatement(AstNode astNode) { + Token tryKeyword = astNode.getFirstChild(PythonKeyword.TRY).getToken(); + List tryBody = getStatementsFromSuite(astNode.getFirstChild(PythonGrammar.SUITE)); + List exceptClauseTrees = astNode.getChildren(PythonGrammar.EXCEPT_CLAUSE).stream() + .map(except -> { + AstNode suite = except.getNextSibling().getNextSibling(); + return exceptClause(except, getStatementsFromSuite(suite)); + }) + .collect(Collectors.toList()); + PyFinallyClauseTree finallyClause = null; + AstNode finallyNode = astNode.getFirstChild(PythonKeyword.FINALLY); + if (finallyNode != null) { + AstNode finallySuite = finallyNode.getNextSibling().getNextSibling(); + List body = getStatementsFromSuite(finallySuite); + finallyClause = new PyFinallyClauseTreeImpl(finallySuite, finallyNode.getToken(), body); + } + PyElseStatementTree elseStatementTree = null; + AstNode elseNode = astNode.getFirstChild(PythonKeyword.ELSE); + if (elseNode != null) { + elseStatementTree = elseStatement(elseNode.getNextSibling().getNextSibling()); + } + return new PyTryStatementTreeImpl(astNode, tryKeyword, tryBody, exceptClauseTrees, finallyClause, elseStatementTree); + } + + private PyExceptClauseTree exceptClause(AstNode except, List body) { + Token exceptKeyword = except.getFirstChild(PythonKeyword.EXCEPT).getToken(); + AstNode exceptionNode = except.getFirstChild(PythonGrammar.TEST); + if (exceptionNode == null) { + return new PyExceptClauseTreeImpl(except, exceptKeyword, body); + } + AstNode asNode = except.getFirstChild(PythonKeyword.AS); + AstNode commaNode = except.getFirstChild(PythonPunctuator.COMMA); + if (asNode != null || commaNode != null) { + PyExpressionTree exceptionInstance = expression(except.getLastChild(PythonGrammar.TEST)); + return new PyExceptClauseTreeImpl(except, exceptKeyword, body, expression(exceptionNode), asNode, commaNode, exceptionInstance); + } + return new PyExceptClauseTreeImpl(except, exceptKeyword, body, expression(exceptionNode)); + } + // expressions private List expressionsFromTest(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index a1330b401e..13e9ecec65 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -61,6 +61,7 @@ public void verify_expected_statement() { testData.put("nonlocal foo", PyNonlocalStatementTree.class); testData.put("while cond: pass", PyWhileStatementTree.class); testData.put("'foo'", PyExpressionStatementTree.class); + testData.put("try: this\nexcept Exception: pass", PyTryStatementTree.class); testData.forEach((c,clazz) -> { AstNode astNode = p.parse(c); @@ -548,4 +549,60 @@ public void expression_statement() { expressionStatement = new PythonTreeMaker().expressionStatement(astNode); assertThat(expressionStatement.expressions()).hasSize(2); } + + @Test + public void try_statement() { + setRootRule(PythonGrammar.TRY_STMT); + AstNode astNode = p.parse("try: pass\nexcept Error: pass"); + PyTryStatementTree tryStatement = new PythonTreeMaker().tryStatement(astNode); + assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); + assertThat(tryStatement.body()).hasSize(1); + assertThat(tryStatement.elseClause()).isNull(); + assertThat(tryStatement.finallyClause()).isNull(); + assertThat(tryStatement.exceptClauses()).hasSize(1); + assertThat(tryStatement.exceptClauses().get(0).exceptKeyword().getValue()).isEqualTo("except"); + assertThat(tryStatement.exceptClauses().get(0).body()).hasSize(1); + + astNode = p.parse("try: pass\nexcept Error: pass\nexcept Error: pass"); + tryStatement = new PythonTreeMaker().tryStatement(astNode); + assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); + assertThat(tryStatement.elseClause()).isNull(); + assertThat(tryStatement.finallyClause()).isNull(); + assertThat(tryStatement.exceptClauses()).hasSize(2); + + astNode = p.parse("try: pass\nexcept Error: pass\nfinally: pass"); + tryStatement = new PythonTreeMaker().tryStatement(astNode); + assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); + assertThat(tryStatement.elseClause()).isNull(); + assertThat(tryStatement.exceptClauses()).hasSize(1); + assertThat(tryStatement.finallyClause()).isNotNull(); + assertThat(tryStatement.finallyClause().finallyKeyword().getValue()).isEqualTo("finally"); + assertThat(tryStatement.finallyClause().body()).hasSize(1); + + astNode = p.parse("try: pass\nexcept Error: pass\nelse: pass"); + tryStatement = new PythonTreeMaker().tryStatement(astNode); + assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); + assertThat(tryStatement.exceptClauses()).hasSize(1); + assertThat(tryStatement.finallyClause()).isNull(); + assertThat(tryStatement.elseClause().elseKeyword().getValue()).isEqualTo("else"); + assertThat(tryStatement.elseClause().body()).hasSize(1); + + astNode = p.parse("try: pass\nexcept Error as e: pass"); + tryStatement = new PythonTreeMaker().tryStatement(astNode); + assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); + assertThat(tryStatement.exceptClauses()).hasSize(1); + PyExceptClauseTree exceptClause = tryStatement.exceptClauses().get(0); + assertThat(exceptClause.asKeyword().getValue()).isEqualTo("as"); + assertThat(exceptClause.commaToken()).isNull(); + assertThat(exceptClause.exceptionInstance()).isNotNull(); + + astNode = p.parse("try: pass\nexcept Error, e: pass"); + tryStatement = new PythonTreeMaker().tryStatement(astNode); + assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); + assertThat(tryStatement.exceptClauses()).hasSize(1); + exceptClause = tryStatement.exceptClauses().get(0); + assertThat(exceptClause.asKeyword()).isNull(); + assertThat(exceptClause.commaToken().getValue()).isEqualTo(","); + assertThat(exceptClause.exceptionInstance()).isNotNull(); + } } From ed02d19c619cad84c33fec51d1b054298368def9 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Thu, 22 Aug 2019 10:42:00 +0200 Subject: [PATCH 28/35] Handle --- .../python/api/tree/PyForStatementTree.java | 5 +++++ .../python/tree/PyForStatementTreeImpl.java | 17 ++++++++++++++++- .../sonar/python/tree/PythonTreeMaker.java | 19 ++++++++++++++----- .../python/api/tree/PythonTreeMakerTest.java | 17 +++++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java index e4b98687e3..b8366fed88 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyForStatementTree.java @@ -43,4 +43,9 @@ public interface PyForStatementTree extends PyStatementTree { Token elseColon(); List elseBody(); + + boolean isAsync(); + + @CheckForNull + Token asyncKeyword(); } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyForStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyForStatementTreeImpl.java index 3519c8d46b..c87d5304f6 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyForStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyForStatementTreeImpl.java @@ -34,13 +34,17 @@ public class PyForStatementTreeImpl extends PyTree implements PyForStatementTree private final List testExpressions; private final List body; private final List elseBody; + private final Token asyncKeyword; + private final boolean isAsync; - public PyForStatementTreeImpl(AstNode astNode, List expressions, List testExpressions, List body, List elseBody) { + public PyForStatementTreeImpl(AstNode astNode, List expressions, List testExpressions, List body, List elseBody, Token asyncKeyword) { super(astNode); this.expressions = expressions; this.testExpressions = testExpressions; this.body = body; this.elseBody = elseBody; + this.asyncKeyword = asyncKeyword; + this.isAsync = asyncKeyword != null; } @Override @@ -100,4 +104,15 @@ public Token elseColon() { public List elseBody() { return elseBody; } + + @Override + public boolean isAsync() { + return isAsync; + } + + @CheckForNull + @Override + public Token asyncKeyword() { + return asyncKeyword; + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 423aa546a6..60eeec5dd5 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -134,6 +134,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.TRY_STMT)) { return tryStatement(astNode); } + if (astNode.is(PythonGrammar.ASYNC_STMT) && astNode.hasDirectChildren(PythonGrammar.FOR_STMT)) { + return forStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -387,13 +390,19 @@ private PyNameTree name(AstNode astNode) { } public PyForStatementTree forStatement(AstNode astNode) { - List expressions = expressionsFromExprList(astNode.getFirstChild(PythonGrammar.EXPRLIST)); - List testExpressions = expressionsFromTest(astNode.getFirstChild(PythonGrammar.TESTLIST)); - AstNode firstSuite = astNode.getFirstChild(PythonGrammar.SUITE); + AstNode forStatementNode = astNode; + Token asyncToken = null; + if (astNode.is(PythonGrammar.ASYNC_STMT)) { + asyncToken = astNode.getFirstChild().getToken(); + forStatementNode = astNode.getFirstChild(PythonGrammar.FOR_STMT); + } + List expressions = expressionsFromExprList(forStatementNode.getFirstChild(PythonGrammar.EXPRLIST)); + List testExpressions = expressionsFromTest(forStatementNode.getFirstChild(PythonGrammar.TESTLIST)); + AstNode firstSuite = forStatementNode.getFirstChild(PythonGrammar.SUITE); List body = getStatementsFromSuite(firstSuite); - AstNode lastSuite = astNode.getLastChild(PythonGrammar.SUITE); + AstNode lastSuite = forStatementNode.getLastChild(PythonGrammar.SUITE); List elseBody = lastSuite == firstSuite ? Collections.emptyList() : getStatementsFromSuite(lastSuite); - return new PyForStatementTreeImpl(astNode, expressions, testExpressions, body, elseBody); + return new PyForStatementTreeImpl(forStatementNode, expressions, testExpressions, body, elseBody, asyncToken); } public PyWhileStatementTreeImpl whileStatement(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 13e9ecec65..25a45b2f5a 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -57,6 +57,7 @@ public void verify_expected_statement() { testData.put("from foo import f", PyImportStatementTree.class); testData.put("class toto:pass", PyClassDefTree.class); testData.put("for foo in bar:pass", PyForStatementTree.class); + testData.put("async for foo in bar: pass", PyForStatementTree.class); testData.put("global foo", PyGlobalStatementTree.class); testData.put("nonlocal foo", PyNonlocalStatementTree.class); testData.put("while cond: pass", PyWhileStatementTree.class); @@ -508,6 +509,8 @@ public void for_statement() { assertThat(pyForStatementTree.body()).hasSize(1); assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); assertThat(pyForStatementTree.elseBody()).isEmpty(); + assertThat(pyForStatementTree.isAsync()).isFalse(); + assertThat(pyForStatementTree.asyncKeyword()).isNull(); astNode = p.parse("for foo in bar:\n pass\nelse:\n pass"); pyForStatementTree = new PythonTreeMaker().forStatement(astNode); @@ -605,4 +608,18 @@ public void try_statement() { assertThat(exceptClause.commaToken().getValue()).isEqualTo(","); assertThat(exceptClause.exceptionInstance()).isNotNull(); } + + @Test + public void async_statement() { + setRootRule(PythonGrammar.ASYNC_STMT); + AstNode astNode = p.parse("async for foo in bar: pass"); + PyForStatementTree pyForStatementTree = new PythonTreeMaker().forStatement(astNode); + assertThat(pyForStatementTree.isAsync()).isTrue(); + assertThat(pyForStatementTree.asyncKeyword().getValue()).isEqualTo("async"); + assertThat(pyForStatementTree.expressions()).hasSize(1); + assertThat(pyForStatementTree.testExpressions()).hasSize(1); + assertThat(pyForStatementTree.body()).hasSize(1); + assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(pyForStatementTree.elseBody()).isEmpty(); + } } From 574412ef2a7aaa35aaa6ee6fe0ecd6a71c7eb9e9 Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Thu, 22 Aug 2019 10:45:29 +0200 Subject: [PATCH 29/35] Add with statement --- .../sonar/python/api/tree/PyTreeVisitor.java | 4 + .../sonar/python/api/tree/PyWithItemTree.java | 36 ++++ .../python/api/tree/PyWithStatementTree.java | 32 ++++ .../java/org/sonar/python/api/tree/Tree.java | 4 + .../sonar/python/tree/BaseTreeVisitor.java | 14 ++ .../python/tree/PyWithStatementTreeImpl.java | 111 ++++++++++++ .../sonar/python/tree/PythonTreeMaker.java | 30 ++++ .../python/api/tree/PythonTreeMakerTest.java | 162 +++++++++++------- 8 files changed, 327 insertions(+), 66 deletions(-) create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyWithItemTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/api/tree/PyWithStatementTree.java create mode 100644 python-squid/src/main/java/org/sonar/python/tree/PyWithStatementTreeImpl.java diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java index 30e4a66b2e..ac74be4a18 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyTreeVisitor.java @@ -78,4 +78,8 @@ public interface PyTreeVisitor { void visitFinallyClause(PyFinallyClauseTree pyFinallyClauseTree); void visitExceptClause(PyExceptClauseTree pyExceptClauseTree); + + void visitWithStatement(PyWithStatementTree pyWithStatementTree); + + void visitWithItem(PyWithItemTree pyWithItemTree); } diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyWithItemTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyWithItemTree.java new file mode 100644 index 0000000000..b8318543e5 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyWithItemTree.java @@ -0,0 +1,36 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import javax.annotation.CheckForNull; + +public interface PyWithItemTree extends Tree { + + PyExpressionTree test(); + + @CheckForNull + Token as(); + + @CheckForNull + PyExpressionTree expression(); + + +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyWithStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyWithStatementTree.java new file mode 100644 index 0000000000..95090a4ce9 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyWithStatementTree.java @@ -0,0 +1,32 @@ +/* + * 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.api.tree; + +import com.sonar.sslr.api.Token; +import java.util.List; + +public interface PyWithStatementTree extends PyStatementTree { + + List withItems(); + + Token colon(); + + List statements(); +} diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java index 37970d86b0..8b8e0e375f 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/Tree.java @@ -86,6 +86,10 @@ enum Kind { WHILE_STMT(PyWhileStatementTree.class), + WITH_ITEM(PyWithItemTree.class), + + WITH_STMT(PyWithStatementTree.class), + YIELD_EXPR(PyYieldExpressionTree.class), YIELD_STMT(PyYieldStatementTree.class); diff --git a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java index 5b30d51f02..569eab8d88 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java +++ b/python-squid/src/main/java/org/sonar/python/tree/BaseTreeVisitor.java @@ -49,6 +49,8 @@ import org.sonar.python.api.tree.PyTreeVisitor; import org.sonar.python.api.tree.PyTryStatementTree; import org.sonar.python.api.tree.PyWhileStatementTree; +import org.sonar.python.api.tree.PyWithItemTree; +import org.sonar.python.api.tree.PyWithStatementTree; import org.sonar.python.api.tree.PyYieldExpressionTree; import org.sonar.python.api.tree.PyYieldStatementTree; import org.sonar.python.api.tree.Tree; @@ -240,4 +242,16 @@ public void visitExceptClause(PyExceptClauseTree pyExceptClauseTree) { scan(pyExceptClauseTree.exceptionInstance()); scan(pyExceptClauseTree.body()); } + + @Override + public void visitWithStatement(PyWithStatementTree pyWithStatementTree) { + scan(pyWithStatementTree.withItems()); + scan(pyWithStatementTree.statements()); + } + + @Override + public void visitWithItem(PyWithItemTree pyWithItemTree) { + scan(pyWithItemTree.test()); + scan(pyWithItemTree.expression()); + } } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyWithStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyWithStatementTreeImpl.java new file mode 100644 index 0000000000..8788a9d976 --- /dev/null +++ b/python-squid/src/main/java/org/sonar/python/tree/PyWithStatementTreeImpl.java @@ -0,0 +1,111 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyStatementTree; +import org.sonar.python.api.tree.PyTreeVisitor; +import org.sonar.python.api.tree.PyWithItemTree; +import org.sonar.python.api.tree.PyWithStatementTree; + +public class PyWithStatementTreeImpl extends PyTree implements PyWithStatementTree { + + private final List withItems; + private final List statements; + private final Token colon; + + public PyWithStatementTreeImpl(AstNode node, List withItems, Token colon, List statements) { + super(node); + this.withItems = withItems; + this.colon = colon; + this.statements = statements; + } + + @Override + public List withItems() { + return withItems; + } + + @Override + public Token colon() { + return colon; + } + + @Override + public List statements() { + return statements; + } + + @Override + public Kind getKind() { + return Kind.WITH_STMT; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitWithStatement(this); + } + + public static class PyWithItemTreeImpl extends PyTree implements PyWithItemTree { + + private final PyExpressionTree test; + private final Token as; + private final PyExpressionTree expr; + + public PyWithItemTreeImpl(AstNode node, PyExpressionTree test, @Nullable Token as, @Nullable PyExpressionTree expr) { + super(node); + this.test = test; + this.as = as; + this.expr = expr; + } + + @Override + public PyExpressionTree test() { + return test; + } + + @CheckForNull + @Override + public Token as() { + return as; + } + + @CheckForNull + @Override + public PyExpressionTree expression() { + return expr; + } + + @Override + public Kind getKind() { + return Kind.WITH_ITEM; + } + + @Override + public void accept(PyTreeVisitor visitor) { + visitor.visitWithItem(this); + } + } +} diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 60eeec5dd5..5d42c30bb2 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -60,6 +60,8 @@ import org.sonar.python.api.tree.PyStatementTree; import org.sonar.python.api.tree.PyTryStatementTree; import org.sonar.python.api.tree.PyTypedArgListTree; +import org.sonar.python.api.tree.PyWithItemTree; +import org.sonar.python.api.tree.PyWithStatementTree; import org.sonar.python.api.tree.PyYieldExpressionTree; import org.sonar.python.api.tree.PyYieldStatementTree; @@ -137,6 +139,9 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.ASYNC_STMT) && astNode.hasDirectChildren(PythonGrammar.FOR_STMT)) { return forStatement(astNode); } + if (astNode.is(PythonGrammar.WITH_STMT)) { + return withStatement(astNode); + } // throw new IllegalStateException("Statement not translated to strongly typed AST"); return null; } @@ -446,6 +451,31 @@ public PyTryStatementTree tryStatement(AstNode astNode) { return new PyTryStatementTreeImpl(astNode, tryKeyword, tryBody, exceptClauseTrees, finallyClause, elseStatementTree); } + public PyWithStatementTree withStatement(AstNode astNode) { + List withItems = withItems(astNode.getChildren(PythonGrammar.WITH_ITEM)); + AstNode suite = astNode.getFirstChild(PythonGrammar.SUITE); + Token colon = suite.getPreviousSibling().getToken(); + List statements = getStatementsFromSuite(suite); + return new PyWithStatementTreeImpl(astNode, withItems, colon, statements); + } + + private List withItems(List withItems) { + return withItems.stream().map(this::withItem).collect(Collectors.toList()); + } + + private PyWithItemTree withItem(AstNode withItem) { + AstNode testNode = withItem.getFirstChild(PythonGrammar.TEST); + PyExpressionTree test = expression(testNode); + AstNode asNode = testNode.getNextSibling(); + PyExpressionTree expr = null; + Token as = null; + if(asNode != null) { + as = asNode.getToken(); + expr = expression(withItem.getFirstChild(PythonGrammar.EXPR)); + } + return new PyWithStatementTreeImpl.PyWithItemTreeImpl(withItem, test, as, expr); + } + private PyExceptClauseTree exceptClause(AstNode except, List body) { Token exceptKeyword = except.getFirstChild(PythonKeyword.EXCEPT).getToken(); AstNode exceptionNode = except.getFirstChild(PythonGrammar.TEST); diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index 25a45b2f5a..e422464205 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -22,6 +22,7 @@ import com.sonar.sslr.api.AstNode; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import org.junit.Test; import org.sonar.python.api.PythonGrammar; import org.sonar.python.parser.RuleTest; @@ -32,10 +33,11 @@ public class PythonTreeMakerTest extends RuleTest { + private final PythonTreeMaker treeMaker = new PythonTreeMaker(); + @Test public void fileInputTreeOnEmptyFile() { - AstNode astNode = p.parse(""); - PyFileInputTree pyTree = new PythonTreeMaker().fileInput(astNode); + PyFileInputTree pyTree = parse("", treeMaker::fileInput); assertThat(pyTree.statements()).isEmpty(); } @@ -63,10 +65,10 @@ public void verify_expected_statement() { testData.put("while cond: pass", PyWhileStatementTree.class); testData.put("'foo'", PyExpressionStatementTree.class); testData.put("try: this\nexcept Exception: pass", PyTryStatementTree.class); + testData.put("with foo, bar as qix : pass", PyWithStatementTree.class); testData.forEach((c,clazz) -> { - AstNode astNode = p.parse(c); - PyFileInputTree pyTree = new PythonTreeMaker().fileInput(astNode); + PyFileInputTree pyTree = parse(c, treeMaker::fileInput); assertThat(pyTree.statements()).hasSize(1); assertThat(pyTree.statements().get(0)).as(c).isInstanceOf(clazz); }); @@ -75,8 +77,7 @@ public void verify_expected_statement() { @Test public void IfStatement() { setRootRule(PythonGrammar.IF_STMT); - AstNode astNode = p.parse("if x: pass"); - PyIfStatementTree pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); + PyIfStatementTree pyIfStatementTree = parse("if x: pass", treeMaker::ifStatement); assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); assertThat(pyIfStatementTree.isElif()).isFalse(); @@ -84,8 +85,8 @@ public void IfStatement() { assertThat(pyIfStatementTree.elseBranch()).isNull(); assertThat(pyIfStatementTree.body()).hasSize(1); - astNode = p.parse("if x: pass\nelse: pass"); - pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); + + pyIfStatementTree = parse("if x: pass\nelse: pass", treeMaker::ifStatement); assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); assertThat(pyIfStatementTree.isElif()).isFalse(); @@ -95,8 +96,8 @@ public void IfStatement() { assertThat(elseBranch.elseKeyword().getValue()).isEqualTo("else"); assertThat(elseBranch.body()).hasSize(1); - astNode = p.parse("if x: pass\nelif y: pass"); - pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); + + pyIfStatementTree = parse("if x: pass\nelif y: pass", treeMaker::ifStatement); assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); assertThat(pyIfStatementTree.isElif()).isFalse(); @@ -109,8 +110,7 @@ public void IfStatement() { assertThat(elif.elifBranches()).isEmpty(); assertThat(elif.body()).hasSize(1); - astNode = p.parse("if x:\n pass"); - pyIfStatementTree = new PythonTreeMaker().ifStatement(astNode); + pyIfStatementTree = parse("if x:\n pass", treeMaker::ifStatement); assertThat(pyIfStatementTree.keyword().getValue()).isEqualTo("if"); assertThat(pyIfStatementTree.condition()).isInstanceOf(PyExpressionTree.class); assertThat(pyIfStatementTree.isElif()).isFalse(); @@ -123,19 +123,19 @@ public void IfStatement() { public void printStatement() { setRootRule(PythonGrammar.PRINT_STMT); AstNode astNode = p.parse("print 'foo'"); - PyPrintStatementTree printStmt = new PythonTreeMaker().printStatement(astNode); + PyPrintStatementTree printStmt = treeMaker.printStatement(astNode); assertThat(printStmt).isNotNull(); assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); assertThat(printStmt.expressions()).hasSize(1); astNode = p.parse("print 'foo', 'bar'"); - printStmt = new PythonTreeMaker().printStatement(astNode); + printStmt = treeMaker.printStatement(astNode); assertThat(printStmt).isNotNull(); assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); assertThat(printStmt.expressions()).hasSize(2); astNode = p.parse("print >> 'foo'"); - printStmt = new PythonTreeMaker().printStatement(astNode); + printStmt = treeMaker.printStatement(astNode); assertThat(printStmt).isNotNull(); assertThat(printStmt.printKeyword().getValue()).isEqualTo("print"); assertThat(printStmt.expressions()).hasSize(1); @@ -145,7 +145,7 @@ public void printStatement() { public void execStatement() { setRootRule(PythonGrammar.EXEC_STMT); AstNode astNode = p.parse("exec 'foo'"); - PyExecStatementTree execStatement = new PythonTreeMaker().execStatement(astNode); + PyExecStatementTree execStatement = treeMaker.execStatement(astNode); assertThat(execStatement).isNotNull(); assertThat(execStatement.execKeyword().getValue()).isEqualTo("exec"); assertThat(execStatement.expression()).isNotNull(); @@ -153,7 +153,7 @@ public void execStatement() { assertThat(execStatement.localsExpression()).isNull(); astNode = p.parse("exec 'foo' in globals"); - execStatement = new PythonTreeMaker().execStatement(astNode); + execStatement = treeMaker.execStatement(astNode); assertThat(execStatement).isNotNull(); assertThat(execStatement.execKeyword().getValue()).isEqualTo("exec"); assertThat(execStatement.expression()).isNotNull(); @@ -161,7 +161,7 @@ public void execStatement() { assertThat(execStatement.localsExpression()).isNull(); astNode = p.parse("exec 'foo' in globals, locals"); - execStatement = new PythonTreeMaker().execStatement(astNode); + execStatement = treeMaker.execStatement(astNode); assertThat(execStatement).isNotNull(); assertThat(execStatement.execKeyword().getValue()).isEqualTo("exec"); assertThat(execStatement.expression()).isNotNull(); @@ -175,13 +175,13 @@ public void execStatement() { public void assertStatement() { setRootRule(PythonGrammar.ASSERT_STMT); AstNode astNode = p.parse("assert x"); - PyAssertStatementTree assertStatement = new PythonTreeMaker().assertStatement(astNode); + PyAssertStatementTree assertStatement = treeMaker.assertStatement(astNode); assertThat(assertStatement).isNotNull(); assertThat(assertStatement.assertKeyword().getValue()).isEqualTo("assert"); assertThat(assertStatement.expressions()).hasSize(1); astNode = p.parse("assert x, y"); - assertStatement = new PythonTreeMaker().assertStatement(astNode); + assertStatement = treeMaker.assertStatement(astNode); assertThat(assertStatement).isNotNull(); assertThat(assertStatement.assertKeyword().getValue()).isEqualTo("assert"); assertThat(assertStatement.expressions()).hasSize(2); @@ -191,7 +191,7 @@ public void assertStatement() { public void passStatement() { setRootRule(PythonGrammar.PASS_STMT); AstNode astNode = p.parse("pass"); - PyPassStatementTree passStatement = new PythonTreeMaker().passStatement(astNode); + PyPassStatementTree passStatement = treeMaker.passStatement(astNode); assertThat(passStatement).isNotNull(); assertThat(passStatement.passKeyword().getValue()).isEqualTo("pass"); } @@ -200,19 +200,19 @@ public void passStatement() { public void delStatement() { setRootRule(PythonGrammar.DEL_STMT); AstNode astNode = p.parse("del foo"); - PyDelStatementTree passStatement = new PythonTreeMaker().delStatement(astNode); + PyDelStatementTree passStatement = treeMaker.delStatement(astNode); assertThat(passStatement).isNotNull(); assertThat(passStatement.delKeyword().getValue()).isEqualTo("del"); assertThat(passStatement.expressions()).hasSize(1); astNode = p.parse("del foo, bar"); - passStatement = new PythonTreeMaker().delStatement(astNode); + passStatement = treeMaker.delStatement(astNode); assertThat(passStatement).isNotNull(); assertThat(passStatement.delKeyword().getValue()).isEqualTo("del"); assertThat(passStatement.expressions()).hasSize(2); astNode = p.parse("del *foo"); - passStatement = new PythonTreeMaker().delStatement(astNode); + passStatement = treeMaker.delStatement(astNode); assertThat(passStatement).isNotNull(); assertThat(passStatement.delKeyword().getValue()).isEqualTo("del"); assertThat(passStatement.expressions()).hasSize(1); @@ -222,19 +222,19 @@ public void delStatement() { public void returnStatement() { setRootRule(PythonGrammar.RETURN_STMT); AstNode astNode = p.parse("return foo"); - PyReturnStatementTree returnStatement = new PythonTreeMaker().returnStatement(astNode); + PyReturnStatementTree returnStatement = treeMaker.returnStatement(astNode); assertThat(returnStatement).isNotNull(); assertThat(returnStatement.returnKeyword().getValue()).isEqualTo("return"); assertThat(returnStatement.expressions()).hasSize(1); astNode = p.parse("return foo, bar"); - returnStatement = new PythonTreeMaker().returnStatement(astNode); + returnStatement = treeMaker.returnStatement(astNode); assertThat(returnStatement).isNotNull(); assertThat(returnStatement.returnKeyword().getValue()).isEqualTo("return"); assertThat(returnStatement.expressions()).hasSize(2); astNode = p.parse("return"); - returnStatement = new PythonTreeMaker().returnStatement(astNode); + returnStatement = treeMaker.returnStatement(astNode); assertThat(returnStatement).isNotNull(); assertThat(returnStatement.returnKeyword().getValue()).isEqualTo("return"); assertThat(returnStatement.expressions()).hasSize(0); @@ -244,14 +244,14 @@ public void returnStatement() { public void yieldStatement() { setRootRule(PythonGrammar.YIELD_STMT); AstNode astNode = p.parse("yield foo"); - PyYieldStatementTree yieldStatement = new PythonTreeMaker().yieldStatement(astNode); + PyYieldStatementTree yieldStatement = treeMaker.yieldStatement(astNode); assertThat(yieldStatement).isNotNull(); PyYieldExpressionTree yieldExpression = yieldStatement.yieldExpression(); assertThat(yieldExpression).isInstanceOf(PyYieldExpressionTree.class); assertThat(yieldExpression.expressions()).hasSize(1); astNode = p.parse("yield foo, bar"); - yieldStatement = new PythonTreeMaker().yieldStatement(astNode); + yieldStatement = treeMaker.yieldStatement(astNode); assertThat(yieldStatement).isNotNull(); yieldExpression = yieldStatement.yieldExpression(); assertThat(yieldExpression).isInstanceOf(PyYieldExpressionTree.class); @@ -260,7 +260,7 @@ public void yieldStatement() { assertThat(yieldExpression.expressions()).hasSize(2); astNode = p.parse("yield from foo"); - yieldStatement = new PythonTreeMaker().yieldStatement(astNode); + yieldStatement = treeMaker.yieldStatement(astNode); assertThat(yieldStatement).isNotNull(); yieldExpression = yieldStatement.yieldExpression(); assertThat(yieldExpression).isInstanceOf(PyYieldExpressionTree.class); @@ -269,7 +269,7 @@ public void yieldStatement() { assertThat(yieldExpression.expressions()).hasSize(1); astNode = p.parse("yield"); - yieldStatement = new PythonTreeMaker().yieldStatement(astNode); + yieldStatement = treeMaker.yieldStatement(astNode); assertThat(yieldStatement).isNotNull(); } @@ -277,7 +277,7 @@ public void yieldStatement() { public void raiseStatement() { setRootRule(PythonGrammar.RAISE_STMT); AstNode astNode = p.parse("raise foo"); - PyRaiseStatementTree raiseStatement = new PythonTreeMaker().raiseStatement(astNode); + PyRaiseStatementTree raiseStatement = treeMaker.raiseStatement(astNode); assertThat(raiseStatement).isNotNull(); assertThat(raiseStatement.raiseKeyword().getValue()).isEqualTo("raise"); assertThat(raiseStatement.fromKeyword()).isNull(); @@ -285,7 +285,7 @@ public void raiseStatement() { assertThat(raiseStatement.expressions()).hasSize(1); astNode = p.parse("raise foo, bar"); - raiseStatement = new PythonTreeMaker().raiseStatement(astNode); + raiseStatement = treeMaker.raiseStatement(astNode); assertThat(raiseStatement).isNotNull(); assertThat(raiseStatement.raiseKeyword().getValue()).isEqualTo("raise"); assertThat(raiseStatement.fromKeyword()).isNull(); @@ -293,7 +293,7 @@ public void raiseStatement() { assertThat(raiseStatement.expressions()).hasSize(2); astNode = p.parse("raise foo from bar"); - raiseStatement = new PythonTreeMaker().raiseStatement(astNode); + raiseStatement = treeMaker.raiseStatement(astNode); assertThat(raiseStatement).isNotNull(); assertThat(raiseStatement.raiseKeyword().getValue()).isEqualTo("raise"); assertThat(raiseStatement.fromKeyword().getValue()).isEqualTo("from"); @@ -301,7 +301,7 @@ public void raiseStatement() { assertThat(raiseStatement.expressions()).hasSize(1); astNode = p.parse("raise"); - raiseStatement = new PythonTreeMaker().raiseStatement(astNode); + raiseStatement = treeMaker.raiseStatement(astNode); assertThat(raiseStatement).isNotNull(); assertThat(raiseStatement.raiseKeyword().getValue()).isEqualTo("raise"); assertThat(raiseStatement.fromKeyword()).isNull(); @@ -313,7 +313,7 @@ public void raiseStatement() { public void breakStatement() { setRootRule(PythonGrammar.BREAK_STMT); AstNode astNode = p.parse("break"); - PyBreakStatementTree breakStatement = new PythonTreeMaker().breakStatement(astNode); + PyBreakStatementTree breakStatement = treeMaker.breakStatement(astNode); assertThat(breakStatement).isNotNull(); assertThat(breakStatement.breakKeyword().getValue()).isEqualTo("break"); } @@ -322,7 +322,7 @@ public void breakStatement() { public void continueStatement() { setRootRule(PythonGrammar.CONTINUE_STMT); AstNode astNode = p.parse("continue"); - PyContinueStatementTree continueStatement = new PythonTreeMaker().continueStatement(astNode); + PyContinueStatementTree continueStatement = treeMaker.continueStatement(astNode); assertThat(continueStatement).isNotNull(); assertThat(continueStatement.continueKeyword().getValue()).isEqualTo("continue"); } @@ -331,7 +331,7 @@ public void continueStatement() { public void importStatement() { setRootRule(PythonGrammar.IMPORT_STMT); AstNode astNode = p.parse("import foo"); - PyImportNameTree importStatement = (PyImportNameTree) new PythonTreeMaker().importStatement(astNode); + PyImportNameTree importStatement = (PyImportNameTree) treeMaker.importStatement(astNode); assertThat(importStatement).isNotNull(); assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); assertThat(importStatement.modules()).hasSize(1); @@ -340,7 +340,7 @@ public void importStatement() { assertThat(importedName1.dottedName().names().get(0).name()).isEqualTo("foo"); astNode = p.parse("import foo as f"); - importStatement = (PyImportNameTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportNameTree) treeMaker.importStatement(astNode); assertThat(importStatement).isNotNull(); assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); assertThat(importStatement.modules()).hasSize(1); @@ -351,7 +351,7 @@ public void importStatement() { assertThat(importedName1.alias().name()).isEqualTo("f"); astNode = p.parse("import foo.bar"); - importStatement = (PyImportNameTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportNameTree) treeMaker.importStatement(astNode); assertThat(importStatement).isNotNull(); assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); assertThat(importStatement.modules()).hasSize(1); @@ -361,7 +361,7 @@ public void importStatement() { assertThat(importedName1.dottedName().names().get(1).name()).isEqualTo("bar"); astNode = p.parse("import foo, bar"); - importStatement = (PyImportNameTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportNameTree) treeMaker.importStatement(astNode); assertThat(importStatement).isNotNull(); assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); assertThat(importStatement.modules()).hasSize(2); @@ -377,7 +377,7 @@ public void importStatement() { public void importFromStatement() { setRootRule(PythonGrammar.IMPORT_STMT); AstNode astNode = p.parse("from foo import f"); - PyImportFromTree importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + PyImportFromTree importStatement = (PyImportFromTree) treeMaker.importStatement(astNode); assertThat(importStatement).isNotNull(); assertThat(importStatement.importKeyword().getValue()).isEqualTo("import"); assertThat(importStatement.dottedPrefixForModule()).isEmpty(); @@ -392,26 +392,26 @@ public void importFromStatement() { assertThat(aliasedNameTree.dottedName().names().get(0).name()).isEqualTo("f"); astNode = p.parse("from .foo import f"); - importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportFromTree) treeMaker.importStatement(astNode); assertThat(importStatement.dottedPrefixForModule()).hasSize(1); assertThat(importStatement.dottedPrefixForModule().get(0).getValue()).isEqualTo("."); assertThat(importStatement.module().names().get(0).name()).isEqualTo("foo"); astNode = p.parse("from ..foo import f"); - importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportFromTree) treeMaker.importStatement(astNode); assertThat(importStatement.dottedPrefixForModule()).hasSize(2); assertThat(importStatement.dottedPrefixForModule().get(0).getValue()).isEqualTo("."); assertThat(importStatement.dottedPrefixForModule().get(1).getValue()).isEqualTo("."); assertThat(importStatement.module().names().get(0).name()).isEqualTo("foo"); astNode = p.parse("from . import f"); - importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportFromTree) treeMaker.importStatement(astNode); assertThat(importStatement.dottedPrefixForModule()).hasSize(1); assertThat(importStatement.dottedPrefixForModule().get(0).getValue()).isEqualTo("."); assertThat(importStatement.module()).isNull(); astNode = p.parse("from foo import f as g"); - importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportFromTree) treeMaker.importStatement(astNode); assertThat(importStatement.importedNames()).hasSize(1); aliasedNameTree = importStatement.importedNames().get(0); assertThat(aliasedNameTree.asKeyword().getValue()).isEqualTo("as"); @@ -419,7 +419,7 @@ public void importFromStatement() { assertThat(aliasedNameTree.dottedName().names().get(0).name()).isEqualTo("f"); astNode = p.parse("from foo import f as g, h"); - importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportFromTree) treeMaker.importStatement(astNode); assertThat(importStatement.importedNames()).hasSize(2); PyAliasedNameTree aliasedNameTree1 = importStatement.importedNames().get(0); assertThat(aliasedNameTree1.asKeyword().getValue()).isEqualTo("as"); @@ -432,7 +432,7 @@ public void importFromStatement() { assertThat(aliasedNameTree2.dottedName().names().get(0).name()).isEqualTo("h"); astNode = p.parse("from foo import *"); - importStatement = (PyImportFromTree) new PythonTreeMaker().importStatement(astNode); + importStatement = (PyImportFromTree) treeMaker.importStatement(astNode); assertThat(importStatement.importedNames()).isNull(); assertThat(importStatement.isWildcardImport()).isTrue(); assertThat(importStatement.wildcard().getValue()).isEqualTo("*"); @@ -442,13 +442,13 @@ public void importFromStatement() { public void globalStatement() { setRootRule(PythonGrammar.GLOBAL_STMT); AstNode astNode = p.parse("global foo"); - PyGlobalStatementTree globalStatement = new PythonTreeMaker().globalStatement(astNode); + PyGlobalStatementTree globalStatement = treeMaker.globalStatement(astNode); assertThat(globalStatement.globalKeyword().getValue()).isEqualTo("global"); assertThat(globalStatement.variables()).hasSize(1); assertThat(globalStatement.variables().get(0).name()).isEqualTo("foo"); astNode = p.parse("global foo, bar"); - globalStatement = new PythonTreeMaker().globalStatement(astNode); + globalStatement = treeMaker.globalStatement(astNode); assertThat(globalStatement.globalKeyword().getValue()).isEqualTo("global"); assertThat(globalStatement.variables()).hasSize(2); assertThat(globalStatement.variables().get(0).name()).isEqualTo("foo"); @@ -459,13 +459,13 @@ public void globalStatement() { public void nonlocalStatement() { setRootRule(PythonGrammar.NONLOCAL_STMT); AstNode astNode = p.parse("nonlocal foo"); - PyNonlocalStatementTree nonlocalStatement = new PythonTreeMaker().nonlocalStatement(astNode); + PyNonlocalStatementTree nonlocalStatement = treeMaker.nonlocalStatement(astNode); assertThat(nonlocalStatement.nonlocalKeyword().getValue()).isEqualTo("nonlocal"); assertThat(nonlocalStatement.variables()).hasSize(1); assertThat(nonlocalStatement.variables().get(0).name()).isEqualTo("foo"); astNode = p.parse("nonlocal foo, bar"); - nonlocalStatement = new PythonTreeMaker().nonlocalStatement(astNode); + nonlocalStatement = treeMaker.nonlocalStatement(astNode); assertThat(nonlocalStatement.nonlocalKeyword().getValue()).isEqualTo("nonlocal"); assertThat(nonlocalStatement.variables()).hasSize(2); assertThat(nonlocalStatement.variables().get(0).name()).isEqualTo("foo"); @@ -476,7 +476,7 @@ public void nonlocalStatement() { public void funcdef_statement() { setRootRule(PythonGrammar.FUNCDEF); AstNode astNode = p.parse("def func(): pass"); - PyFunctionDefTree functionDefTree = new PythonTreeMaker().funcDefStatement(astNode); + PyFunctionDefTree functionDefTree = treeMaker.funcDefStatement(astNode); assertThat(functionDefTree.name()).isNotNull(); assertThat(functionDefTree.name().name()).isEqualTo("func"); assertThat(functionDefTree.body()).hasSize(1); @@ -490,7 +490,7 @@ public void funcdef_statement() { public void classdef_statement() { setRootRule(PythonGrammar.CLASSDEF); AstNode astNode = p.parse("class clazz: pass"); - PyClassDefTree classDefTree = new PythonTreeMaker().classDefStatement(astNode); + PyClassDefTree classDefTree = treeMaker.classDefStatement(astNode); assertThat(classDefTree.name()).isNotNull(); assertThat(classDefTree.name().name()).isEqualTo("clazz"); assertThat(classDefTree.body()).hasSize(1); @@ -503,7 +503,7 @@ public void classdef_statement() { public void for_statement() { setRootRule(PythonGrammar.FOR_STMT); AstNode astNode = p.parse("for foo in bar: pass"); - PyForStatementTree pyForStatementTree = new PythonTreeMaker().forStatement(astNode); + PyForStatementTree pyForStatementTree = treeMaker.forStatement(astNode); assertThat(pyForStatementTree.expressions()).hasSize(1); assertThat(pyForStatementTree.testExpressions()).hasSize(1); assertThat(pyForStatementTree.body()).hasSize(1); @@ -513,7 +513,7 @@ public void for_statement() { assertThat(pyForStatementTree.asyncKeyword()).isNull(); astNode = p.parse("for foo in bar:\n pass\nelse:\n pass"); - pyForStatementTree = new PythonTreeMaker().forStatement(astNode); + pyForStatementTree = treeMaker.forStatement(astNode); assertThat(pyForStatementTree.expressions()).hasSize(1); assertThat(pyForStatementTree.testExpressions()).hasSize(1); assertThat(pyForStatementTree.body()).hasSize(1); @@ -526,14 +526,14 @@ public void for_statement() { public void while_statement() { setRootRule(PythonGrammar.WHILE_STMT); AstNode astNode = p.parse("while foo : pass"); - PyWhileStatementTreeImpl pyForStatementTree = new PythonTreeMaker().whileStatement(astNode); + PyWhileStatementTreeImpl pyForStatementTree = treeMaker.whileStatement(astNode); assertThat(pyForStatementTree.condition()).isNotNull(); assertThat(pyForStatementTree.body()).hasSize(1); assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); assertThat(pyForStatementTree.elseBody()).isEmpty(); astNode = p.parse("while foo:\n pass\nelse:\n pass"); - pyForStatementTree = new PythonTreeMaker().whileStatement(astNode); + pyForStatementTree = treeMaker.whileStatement(astNode); assertThat(pyForStatementTree.condition()).isNotNull(); assertThat(pyForStatementTree.body()).hasSize(1); assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); @@ -545,11 +545,11 @@ public void while_statement() { public void expression_statement() { setRootRule(PythonGrammar.EXPRESSION_STMT); AstNode astNode = p.parse("'foo'"); - PyExpressionStatementTree expressionStatement = new PythonTreeMaker().expressionStatement(astNode); + PyExpressionStatementTree expressionStatement = treeMaker.expressionStatement(astNode); assertThat(expressionStatement.expressions()).hasSize(1); astNode = p.parse("'foo', 'bar'"); - expressionStatement = new PythonTreeMaker().expressionStatement(astNode); + expressionStatement = treeMaker.expressionStatement(astNode); assertThat(expressionStatement.expressions()).hasSize(2); } @@ -557,7 +557,7 @@ public void expression_statement() { public void try_statement() { setRootRule(PythonGrammar.TRY_STMT); AstNode astNode = p.parse("try: pass\nexcept Error: pass"); - PyTryStatementTree tryStatement = new PythonTreeMaker().tryStatement(astNode); + PyTryStatementTree tryStatement = treeMaker.tryStatement(astNode); assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); assertThat(tryStatement.body()).hasSize(1); assertThat(tryStatement.elseClause()).isNull(); @@ -567,14 +567,14 @@ public void try_statement() { assertThat(tryStatement.exceptClauses().get(0).body()).hasSize(1); astNode = p.parse("try: pass\nexcept Error: pass\nexcept Error: pass"); - tryStatement = new PythonTreeMaker().tryStatement(astNode); + tryStatement = treeMaker.tryStatement(astNode); assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); assertThat(tryStatement.elseClause()).isNull(); assertThat(tryStatement.finallyClause()).isNull(); assertThat(tryStatement.exceptClauses()).hasSize(2); astNode = p.parse("try: pass\nexcept Error: pass\nfinally: pass"); - tryStatement = new PythonTreeMaker().tryStatement(astNode); + tryStatement = treeMaker.tryStatement(astNode); assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); assertThat(tryStatement.elseClause()).isNull(); assertThat(tryStatement.exceptClauses()).hasSize(1); @@ -583,7 +583,7 @@ public void try_statement() { assertThat(tryStatement.finallyClause().body()).hasSize(1); astNode = p.parse("try: pass\nexcept Error: pass\nelse: pass"); - tryStatement = new PythonTreeMaker().tryStatement(astNode); + tryStatement = treeMaker.tryStatement(astNode); assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); assertThat(tryStatement.exceptClauses()).hasSize(1); assertThat(tryStatement.finallyClause()).isNull(); @@ -591,7 +591,7 @@ public void try_statement() { assertThat(tryStatement.elseClause().body()).hasSize(1); astNode = p.parse("try: pass\nexcept Error as e: pass"); - tryStatement = new PythonTreeMaker().tryStatement(astNode); + tryStatement = treeMaker.tryStatement(astNode); assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); assertThat(tryStatement.exceptClauses()).hasSize(1); PyExceptClauseTree exceptClause = tryStatement.exceptClauses().get(0); @@ -600,7 +600,7 @@ public void try_statement() { assertThat(exceptClause.exceptionInstance()).isNotNull(); astNode = p.parse("try: pass\nexcept Error, e: pass"); - tryStatement = new PythonTreeMaker().tryStatement(astNode); + tryStatement = treeMaker.tryStatement(astNode); assertThat(tryStatement.tryKeyword().getValue()).isEqualTo("try"); assertThat(tryStatement.exceptClauses()).hasSize(1); exceptClause = tryStatement.exceptClauses().get(0); @@ -622,4 +622,34 @@ public void async_statement() { assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); assertThat(pyForStatementTree.elseBody()).isEmpty(); } + + @Test + public void with_statement() { + setRootRule(PythonGrammar.WITH_STMT); + PyWithStatementTree withStatement = parse("with foo : pass", treeMaker::withStatement); + assertThat(withStatement.withItems()).hasSize(1); + PyWithItemTree pyWithItemTree = withStatement.withItems().get(0); + assertThat(pyWithItemTree.test()).isNotNull(); + assertThat(pyWithItemTree.as()).isNull(); + assertThat(pyWithItemTree.expression()).isNull(); + assertThat(withStatement.statements()).hasSize(1); + assertThat(withStatement.statements().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + + withStatement = parse("with foo as bar, qix : pass", treeMaker::withStatement); + assertThat(withStatement.withItems()).hasSize(2); + pyWithItemTree = withStatement.withItems().get(0); + assertThat(pyWithItemTree.test()).isNotNull(); + assertThat(pyWithItemTree.as()).isNotNull(); + assertThat(pyWithItemTree.expression()).isNotNull(); + pyWithItemTree = withStatement.withItems().get(1); + assertThat(pyWithItemTree.test()).isNotNull(); + assertThat(pyWithItemTree.as()).isNull(); + assertThat(pyWithItemTree.expression()).isNull(); + assertThat(withStatement.statements()).hasSize(1); + assertThat(withStatement.statements().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + } + + private T parse(String code, Function func) { + return func.apply(p.parse(code)); + } } From bd16f3ef538e8d8499837756e69dd13632a00b93 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Thu, 22 Aug 2019 11:58:53 +0200 Subject: [PATCH 30/35] Handle `async with` --- .../python/api/tree/PyWithStatementTree.java | 6 ++++++ .../python/tree/PyWithStatementTreeImpl.java | 17 +++++++++++++++- .../sonar/python/tree/PythonTreeMaker.java | 20 +++++++++++++------ .../python/api/tree/PythonTreeMakerTest.java | 13 ++++++++++++ 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/python-squid/src/main/java/org/sonar/python/api/tree/PyWithStatementTree.java b/python-squid/src/main/java/org/sonar/python/api/tree/PyWithStatementTree.java index 95090a4ce9..7d543d9883 100644 --- a/python-squid/src/main/java/org/sonar/python/api/tree/PyWithStatementTree.java +++ b/python-squid/src/main/java/org/sonar/python/api/tree/PyWithStatementTree.java @@ -21,6 +21,7 @@ import com.sonar.sslr.api.Token; import java.util.List; +import javax.annotation.CheckForNull; public interface PyWithStatementTree extends PyStatementTree { @@ -29,4 +30,9 @@ public interface PyWithStatementTree extends PyStatementTree { Token colon(); List statements(); + + boolean isAsync(); + + @CheckForNull + Token asyncKeyword(); } diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyWithStatementTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyWithStatementTreeImpl.java index 8788a9d976..c02afc1b46 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyWithStatementTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyWithStatementTreeImpl.java @@ -34,13 +34,17 @@ public class PyWithStatementTreeImpl extends PyTree implements PyWithStatementTr private final List withItems; private final List statements; + private final Token asyncKeyword; + private final boolean isAsync; private final Token colon; - public PyWithStatementTreeImpl(AstNode node, List withItems, Token colon, List statements) { + public PyWithStatementTreeImpl(AstNode node, List withItems, Token colon, List statements, Token asyncKeyword) { super(node); this.withItems = withItems; this.colon = colon; this.statements = statements; + this.asyncKeyword = asyncKeyword; + this.isAsync = asyncKeyword != null; } @Override @@ -58,6 +62,17 @@ public List statements() { return statements; } + @Override + public boolean isAsync() { + return isAsync; + } + + @CheckForNull + @Override + public Token asyncKeyword() { + return asyncKeyword; + } + @Override public Kind getKind() { return Kind.WITH_STMT; diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 5d42c30bb2..f869d366a4 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -139,11 +139,13 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.ASYNC_STMT) && astNode.hasDirectChildren(PythonGrammar.FOR_STMT)) { return forStatement(astNode); } + if (astNode.is(PythonGrammar.ASYNC_STMT) && astNode.hasDirectChildren(PythonGrammar.WITH_STMT)) { + return withStatement(astNode); + } if (astNode.is(PythonGrammar.WITH_STMT)) { return withStatement(astNode); } - // throw new IllegalStateException("Statement not translated to strongly typed AST"); - return null; + throw new IllegalStateException("Statement not translated to strongly typed AST"); } private List getStatementsFromSuite(AstNode astNode) { @@ -452,11 +454,17 @@ public PyTryStatementTree tryStatement(AstNode astNode) { } public PyWithStatementTree withStatement(AstNode astNode) { - List withItems = withItems(astNode.getChildren(PythonGrammar.WITH_ITEM)); - AstNode suite = astNode.getFirstChild(PythonGrammar.SUITE); + AstNode withStmtNode = astNode; + Token asyncKeyword = null; + if (astNode.is(PythonGrammar.ASYNC_STMT)) { + withStmtNode = astNode.getFirstChild(PythonGrammar.WITH_STMT); + asyncKeyword = astNode.getFirstChild().getToken(); + } + List withItems = withItems(withStmtNode.getChildren(PythonGrammar.WITH_ITEM)); + AstNode suite = withStmtNode.getFirstChild(PythonGrammar.SUITE); Token colon = suite.getPreviousSibling().getToken(); List statements = getStatementsFromSuite(suite); - return new PyWithStatementTreeImpl(astNode, withItems, colon, statements); + return new PyWithStatementTreeImpl(withStmtNode, withItems, colon, statements, asyncKeyword); } private List withItems(List withItems) { @@ -469,7 +477,7 @@ private PyWithItemTree withItem(AstNode withItem) { AstNode asNode = testNode.getNextSibling(); PyExpressionTree expr = null; Token as = null; - if(asNode != null) { + if (asNode != null) { as = asNode.getToken(); expr = expression(withItem.getFirstChild(PythonGrammar.EXPR)); } diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java index e422464205..e4c9546a41 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java @@ -66,6 +66,7 @@ public void verify_expected_statement() { testData.put("'foo'", PyExpressionStatementTree.class); testData.put("try: this\nexcept Exception: pass", PyTryStatementTree.class); testData.put("with foo, bar as qix : pass", PyWithStatementTree.class); + testData.put("async with foo, bar as qix : pass", PyWithStatementTree.class); testData.forEach((c,clazz) -> { PyFileInputTree pyTree = parse(c, treeMaker::fileInput); @@ -621,12 +622,24 @@ public void async_statement() { assertThat(pyForStatementTree.body()).hasSize(1); assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); assertThat(pyForStatementTree.elseBody()).isEmpty(); + + PyWithStatementTree withStatement = parse("async with foo : pass", treeMaker::withStatement); + assertThat(withStatement.isAsync()).isTrue(); + assertThat(withStatement.asyncKeyword().getValue()).isEqualTo("async"); + PyWithItemTree pyWithItemTree = withStatement.withItems().get(0); + assertThat(pyWithItemTree.test()).isNotNull(); + assertThat(pyWithItemTree.as()).isNull(); + assertThat(pyWithItemTree.expression()).isNull(); + assertThat(withStatement.statements()).hasSize(1); + assertThat(withStatement.statements().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); } @Test public void with_statement() { setRootRule(PythonGrammar.WITH_STMT); PyWithStatementTree withStatement = parse("with foo : pass", treeMaker::withStatement); + assertThat(withStatement.isAsync()).isFalse(); + assertThat(withStatement.asyncKeyword()).isNull(); assertThat(withStatement.withItems()).hasSize(1); PyWithItemTree pyWithItemTree = withStatement.withItems().get(0); assertThat(pyWithItemTree.test()).isNotNull(); From fbbbce89da1b06f58ef046bb21e8e00af4009437 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Thu, 22 Aug 2019 13:25:14 +0200 Subject: [PATCH 31/35] Improve exception message when a statement is not converted, move PythonTreeMakerTest --- .../sonar/python/tree/PythonTreeMaker.java | 2 +- .../{api => }/tree/PythonTreeMakerTest.java | 35 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) rename python-squid/src/test/java/org/sonar/python/{api => }/tree/PythonTreeMakerTest.java (95%) diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index f869d366a4..21f315cd98 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -145,7 +145,7 @@ private PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.WITH_STMT)) { return withStatement(astNode); } - throw new IllegalStateException("Statement not translated to strongly typed AST"); + throw new IllegalStateException("Statement " + astNode.getType() + " not correctly translated to strongly typed AST"); } private List getStatementsFromSuite(AstNode astNode) { diff --git a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java similarity index 95% rename from python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java rename to python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java index e4c9546a41..55ea1d41d4 100644 --- a/python-squid/src/test/java/org/sonar/python/api/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java @@ -17,7 +17,7 @@ * 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.api.tree; +package org.sonar.python.tree; import com.sonar.sslr.api.AstNode; import java.util.HashMap; @@ -25,9 +25,38 @@ import java.util.function.Function; import org.junit.Test; import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.tree.PyAliasedNameTree; +import org.sonar.python.api.tree.PyAssertStatementTree; +import org.sonar.python.api.tree.PyBreakStatementTree; +import org.sonar.python.api.tree.PyClassDefTree; +import org.sonar.python.api.tree.PyContinueStatementTree; +import org.sonar.python.api.tree.PyDelStatementTree; +import org.sonar.python.api.tree.PyElseStatementTree; +import org.sonar.python.api.tree.PyExceptClauseTree; +import org.sonar.python.api.tree.PyExecStatementTree; +import org.sonar.python.api.tree.PyExpressionStatementTree; +import org.sonar.python.api.tree.PyExpressionTree; +import org.sonar.python.api.tree.PyFileInputTree; +import org.sonar.python.api.tree.PyForStatementTree; +import org.sonar.python.api.tree.PyFunctionDefTree; +import org.sonar.python.api.tree.PyGlobalStatementTree; +import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyImportFromTree; +import org.sonar.python.api.tree.PyImportNameTree; +import org.sonar.python.api.tree.PyImportStatementTree; +import org.sonar.python.api.tree.PyNonlocalStatementTree; +import org.sonar.python.api.tree.PyPassStatementTree; +import org.sonar.python.api.tree.PyPrintStatementTree; +import org.sonar.python.api.tree.PyRaiseStatementTree; +import org.sonar.python.api.tree.PyReturnStatementTree; +import org.sonar.python.api.tree.PyTryStatementTree; +import org.sonar.python.api.tree.PyWhileStatementTree; +import org.sonar.python.api.tree.PyWithItemTree; +import org.sonar.python.api.tree.PyWithStatementTree; +import org.sonar.python.api.tree.PyYieldExpressionTree; +import org.sonar.python.api.tree.PyYieldStatementTree; +import org.sonar.python.api.tree.Tree; import org.sonar.python.parser.RuleTest; -import org.sonar.python.tree.PyWhileStatementTreeImpl; -import org.sonar.python.tree.PythonTreeMaker; import static org.assertj.core.api.Assertions.assertThat; From ff220907758a15adbd8a563793b048e7e1997b9b Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Thu, 22 Aug 2019 14:18:36 +0200 Subject: [PATCH 32/35] Test failure in case of unexpected statement --- .../java/org/sonar/python/tree/PythonTreeMaker.java | 2 +- .../org/sonar/python/tree/PythonTreeMakerTest.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java index 21f315cd98..bdf7d16c1c 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PythonTreeMaker.java @@ -72,7 +72,7 @@ public PyFileInputTree fileInput(AstNode astNode) { return new PyFileInputTreeImpl(astNode, statements); } - private PyStatementTree statement(AstNode astNode) { + PyStatementTree statement(AstNode astNode) { if (astNode.is(PythonGrammar.IF_STMT)) { return ifStatement(astNode); } diff --git a/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java index 55ea1d41d4..ed57fb6854 100644 --- a/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java @@ -59,6 +59,7 @@ import org.sonar.python.parser.RuleTest; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; public class PythonTreeMakerTest extends RuleTest { @@ -70,6 +71,16 @@ public void fileInputTreeOnEmptyFile() { assertThat(pyTree.statements()).isEmpty(); } + @Test + public void unexpected_statement_should_throw_an_exception() { + try { + parse("", treeMaker::statement); + fail("unexpected ASTNode type for statement should not succeed to be translated to Strongly typed AST"); + } catch (IllegalStateException iae) { + assertThat(iae).hasMessage("Statement FILE_INPUT not correctly translated to strongly typed AST"); + } + } + @Test public void verify_expected_statement() { Map> testData = new HashMap<>(); From e779db68abbdb1b16a48c01833fb3347205ae3ae Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Thu, 22 Aug 2019 14:39:44 +0200 Subject: [PATCH 33/35] adding more test for PythonCheckTree --- .../org/sonar/python/PythonCheckTree.java | 8 -- .../org/sonar/python/PythonCheckTreeTest.java | 89 +++++++++++++++++++ 2 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 python-squid/src/test/java/org/sonar/python/PythonCheckTreeTest.java diff --git a/python-squid/src/main/java/org/sonar/python/PythonCheckTree.java b/python-squid/src/main/java/org/sonar/python/PythonCheckTree.java index 227dfcf13d..23a85739e5 100644 --- a/python-squid/src/main/java/org/sonar/python/PythonCheckTree.java +++ b/python-squid/src/main/java/org/sonar/python/PythonCheckTree.java @@ -20,10 +20,6 @@ package org.sonar.python; import com.sonar.sslr.api.Token; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; import javax.annotation.Nullable; import org.sonar.python.api.tree.Tree; import org.sonar.python.tree.BaseTreeVisitor; @@ -48,10 +44,6 @@ protected final PreciseIssue addIssue(Tree node, @Nullable String message) { return newIssue; } - public static Set immutableSet(T... el) { - return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(el))); - } - @Override public void scanFile(PythonVisitorContext visitorContext) { this.context = visitorContext; diff --git a/python-squid/src/test/java/org/sonar/python/PythonCheckTreeTest.java b/python-squid/src/test/java/org/sonar/python/PythonCheckTreeTest.java new file mode 100644 index 0000000000..28642cc2e5 --- /dev/null +++ b/python-squid/src/test/java/org/sonar/python/PythonCheckTreeTest.java @@ -0,0 +1,89 @@ +/* + * 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 java.io.File; +import java.util.List; +import org.junit.Test; +import org.sonar.python.PythonCheck.PreciseIssue; +import org.sonar.python.api.tree.PyFunctionDefTree; +import org.sonar.python.api.tree.PyNameTree; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PythonCheckTreeTest { + + private static final File FILE = new File("src/test/resources/file.py"); + public static final String MESSAGE = "message"; + + private static List scanFileForIssues(File file, PythonCheck check) { + PythonVisitorContext context = TestPythonVisitorRunner.createContext(file); + check.scanFile(context); + return context.getIssues(); + } + + @Test + public void test() { + TestPythonCheck check = new TestPythonCheck (){ + @Override + public void visitFunctionDef(PyFunctionDefTree pyFunctionDefTree) { + super.visitFunctionDef(pyFunctionDefTree); + PyNameTree name = pyFunctionDefTree.name(); + addIssue(name, name.astNode().getTokenValue()); + } + }; + + List issues = scanFileForIssues(FILE, check); + + assertThat(issues).hasSize(2); + PreciseIssue firstIssue = issues.get(0); + + assertThat(firstIssue.cost()).isNull(); + assertThat(firstIssue.secondaryLocations()).isEmpty(); + + IssueLocation primaryLocation = firstIssue.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); + } + + @Test + public void test_cost() { + TestPythonCheck check = new TestPythonCheck (){ + @Override + public void visitFunctionDef(PyFunctionDefTree pyFunctionDefTree) { + super.visitFunctionDef(pyFunctionDefTree); + PyNameTree name = pyFunctionDefTree.name(); + addIssue(name.astNode().getToken(), MESSAGE).withCost(42); + } + }; + + List issues = scanFileForIssues(FILE, check); + PreciseIssue firstIssue = issues.get(0); + assertThat(firstIssue.cost()).isEqualTo(42); + } + + private static class TestPythonCheck extends PythonCheckTree { + + } +} From f95a34f31f777d9c136cbd6bbbe61b6da03a37f8 Mon Sep 17 00:00:00 2001 From: Nicolas PERU Date: Thu, 22 Aug 2019 15:01:08 +0200 Subject: [PATCH 34/35] Add test for missing tokens --- .../python/tree/PythonTreeMakerTest.java | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java index ed57fb6854..dc5fa84cfc 100644 --- a/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java @@ -522,8 +522,16 @@ public void funcdef_statement() { assertThat(functionDefTree.name().name()).isEqualTo("func"); assertThat(functionDefTree.body()).hasSize(1); assertThat(functionDefTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + // TODO assertThat(functionDefTree.typedArgs()).isNull(); assertThat(functionDefTree.decorators()).isNull(); + assertThat(functionDefTree.asyncKeyword()).isNull(); + assertThat(functionDefTree.colon()).isNull(); + assertThat(functionDefTree.defKeyword()).isNull(); + assertThat(functionDefTree.dash()).isNull(); + assertThat(functionDefTree.gt()).isNull(); + assertThat(functionDefTree.leftPar()).isNull(); + assertThat(functionDefTree.rightPar()).isNull(); } @@ -561,25 +569,39 @@ public void for_statement() { assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); assertThat(pyForStatementTree.elseBody()).hasSize(1); assertThat(pyForStatementTree.elseBody().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + + // TODO + assertThat(pyForStatementTree.forKeyword()).isNull(); + assertThat(pyForStatementTree.inKeyword()).isNull(); + assertThat(pyForStatementTree.colon()).isNull(); + assertThat(pyForStatementTree.elseKeyword()).isNull(); + assertThat(pyForStatementTree.elseColon()).isNull(); } @Test public void while_statement() { setRootRule(PythonGrammar.WHILE_STMT); AstNode astNode = p.parse("while foo : pass"); - PyWhileStatementTreeImpl pyForStatementTree = treeMaker.whileStatement(astNode); - assertThat(pyForStatementTree.condition()).isNotNull(); - assertThat(pyForStatementTree.body()).hasSize(1); - assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); - assertThat(pyForStatementTree.elseBody()).isEmpty(); + PyWhileStatementTreeImpl whileStatement = treeMaker.whileStatement(astNode); + assertThat(whileStatement.condition()).isNotNull(); + assertThat(whileStatement.body()).hasSize(1); + assertThat(whileStatement.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(whileStatement.elseBody()).isEmpty(); astNode = p.parse("while foo:\n pass\nelse:\n pass"); - pyForStatementTree = treeMaker.whileStatement(astNode); - assertThat(pyForStatementTree.condition()).isNotNull(); - assertThat(pyForStatementTree.body()).hasSize(1); - assertThat(pyForStatementTree.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); - assertThat(pyForStatementTree.elseBody()).hasSize(1); - assertThat(pyForStatementTree.elseBody().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + whileStatement = treeMaker.whileStatement(astNode); + assertThat(whileStatement.condition()).isNotNull(); + assertThat(whileStatement.body()).hasSize(1); + assertThat(whileStatement.body().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + assertThat(whileStatement.elseBody()).hasSize(1); + assertThat(whileStatement.elseBody().get(0).is(Tree.Kind.PASS_STMT)).isTrue(); + + // TODO + assertThat(whileStatement.whileKeyword()).isNull(); + assertThat(whileStatement.colon()).isNull(); + assertThat(whileStatement.elseKeyword()).isNull(); + assertThat(whileStatement.elseColon()).isNull(); + } @Test From e5fc2ae77b1101ad9e411e6ec0c002baa9d4d777 Mon Sep 17 00:00:00 2001 From: Andrea Guarino Date: Thu, 22 Aug 2019 15:27:43 +0200 Subject: [PATCH 35/35] add test for BaseTreeVisitor --- .../python/tree/BaseTreeVisitorTest.java | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 python-squid/src/test/java/org/sonar/python/tree/BaseTreeVisitorTest.java diff --git a/python-squid/src/test/java/org/sonar/python/tree/BaseTreeVisitorTest.java b/python-squid/src/test/java/org/sonar/python/tree/BaseTreeVisitorTest.java new file mode 100644 index 0000000000..d20e8e17cc --- /dev/null +++ b/python-squid/src/test/java/org/sonar/python/tree/BaseTreeVisitorTest.java @@ -0,0 +1,170 @@ +/* + * 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.tree; + +import com.sonar.sslr.api.AstNode; +import java.util.function.Function; +import org.junit.Test; +import org.sonar.python.api.PythonGrammar; +import org.sonar.python.api.tree.PyAssertStatementTree; +import org.sonar.python.api.tree.PyClassDefTree; +import org.sonar.python.api.tree.PyDelStatementTree; +import org.sonar.python.api.tree.PyExecStatementTree; +import org.sonar.python.api.tree.PyForStatementTree; +import org.sonar.python.api.tree.PyFunctionDefTree; +import org.sonar.python.api.tree.PyIfStatementTree; +import org.sonar.python.api.tree.PyImportFromTree; +import org.sonar.python.api.tree.PyImportNameTree; +import org.sonar.python.api.tree.PyPassStatementTree; +import org.sonar.python.api.tree.PyPrintStatementTree; +import org.sonar.python.api.tree.PyReturnStatementTree; +import org.sonar.python.api.tree.PyTryStatementTree; +import org.sonar.python.api.tree.PyWithStatementTree; +import org.sonar.python.api.tree.PyYieldStatementTree; +import org.sonar.python.parser.RuleTest; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +public class BaseTreeVisitorTest extends RuleTest { + private final PythonTreeMaker treeMaker = new PythonTreeMaker(); + + @Test + public void if_statement() { + setRootRule(PythonGrammar.IF_STMT); + PyIfStatementTree tree = parse("if p1: print 'a'\nelif p2: return\nelse: yield", treeMaker::ifStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitIfStatement(tree); + verify(visitor).visitIfStatement(tree); + verify(visitor).visitIfStatement(tree.elifBranches().get(0)); + verify(visitor).visitPrintStatement((PyPrintStatementTree) tree.body().get(0)); + verify(visitor).visitReturnStatement((PyReturnStatementTree) tree.elifBranches().get(0).body().get(0)); + verify(visitor).visitYieldStatement((PyYieldStatementTree) tree.elseBranch().body().get(0)); + } + + @Test + public void exec_statement() { + setRootRule(PythonGrammar.EXEC_STMT); + PyExecStatementTree tree = parse("exec 'foo' in globals, locals", treeMaker::execStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitExecStatement(tree); + verify(visitor).scan(tree.expression()); + verify(visitor).scan(tree.globalsExpression()); + verify(visitor).scan(tree.localsExpression()); + } + + @Test + public void assert_statement() { + setRootRule(PythonGrammar.ASSERT_STMT); + PyAssertStatementTree tree = parse("assert x, y", treeMaker::assertStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitAssertStatement(tree); + verify(visitor).scan(tree.expressions()); + } + + @Test + public void delete_statement() { + setRootRule(PythonGrammar.DEL_STMT); + PyDelStatementTree tree = parse("del x", treeMaker::delStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitDelStatement(tree); + verify(visitor).scan(tree.expressions()); + } + + @Test + public void fundef_statement() { + setRootRule(PythonGrammar.FUNCDEF); + PyFunctionDefTree pyFunctionDefTree = parse("def foo(): pass", treeMaker::funcDefStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitFunctionDef(pyFunctionDefTree); + verify(visitor).visitName(pyFunctionDefTree.name()); + verify(visitor).visitPassStatement((PyPassStatementTree) pyFunctionDefTree.body().get(0)); + } + + @Test + public void import_statement() { + setRootRule(PythonGrammar.IMPORT_STMT); + PyImportFromTree tree = (PyImportFromTree) parse("from foo import f as g", treeMaker::importStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitImportFrom(tree); + verify(visitor).visitAliasedName(tree.importedNames().get(0)); + verify(visitor).visitDottedName(tree.module()); + + PyImportNameTree pyTree = (PyImportNameTree) parse("import f as g", treeMaker::importStatement); + visitor = spy(BaseTreeVisitor.class); + visitor.visitImportName(pyTree); + verify(visitor).visitAliasedName(pyTree.modules().get(0)); + } + + @Test + public void for_statement() { + setRootRule(PythonGrammar.FOR_STMT); + PyForStatementTree tree = parse("for foo in bar:pass\nelse: pass", treeMaker::forStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitForStatement(tree); + verify(visitor).visitPassStatement((PyPassStatementTree) tree.body().get(0)); + verify(visitor).visitPassStatement((PyPassStatementTree) tree.elseBody().get(0)); + } + + @Test + public void while_statement() { + setRootRule(PythonGrammar.WHILE_STMT); + PyWhileStatementTreeImpl tree = parse("while foo:\n pass\nelse:\n pass", treeMaker::whileStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitWhileStatement(tree); + verify(visitor).visitPassStatement((PyPassStatementTree) tree.body().get(0)); + verify(visitor).visitPassStatement((PyPassStatementTree) tree.elseBody().get(0)); + } + + @Test + public void try_statement() { + setRootRule(PythonGrammar.TRY_STMT); + PyTryStatementTree tree = parse("try: pass\nexcept Error: pass\nfinally: pass", treeMaker::tryStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitTryStatement(tree); + verify(visitor).visitFinallyClause(tree.finallyClause()); + verify(visitor).visitExceptClause(tree.exceptClauses().get(0)); + verify(visitor).visitPassStatement((PyPassStatementTree) tree.body().get(0)); + } + + @Test + public void with_statement() { + setRootRule(PythonGrammar.WITH_STMT); + PyWithStatementTree tree = parse("with foo as bar, qix : pass", treeMaker::withStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitWithStatement(tree); + verify(visitor).visitWithItem(tree.withItems().get(0)); + verify(visitor).visitPassStatement((PyPassStatementTree) tree.statements().get(0)); + } + + @Test + public void class_statement() { + setRootRule(PythonGrammar.CLASSDEF); + PyClassDefTree tree = parse("class clazz: pass", treeMaker::classDefStatement); + BaseTreeVisitor visitor = spy(BaseTreeVisitor.class); + visitor.visitClassDef(tree); + verify(visitor).visitName(tree.name()); + verify(visitor).visitPassStatement((PyPassStatementTree) tree.body().get(0)); + } + + private T parse(String code, Function func) { + return func.apply(p.parse(code)); + } +}