diff --git a/php-frontend/src/main/java/org/sonar/php/parser/PHPGrammar.java b/php-frontend/src/main/java/org/sonar/php/parser/PHPGrammar.java index 0694f28c8a..7068804778 100644 --- a/php-frontend/src/main/java/org/sonar/php/parser/PHPGrammar.java +++ b/php-frontend/src/main/java/org/sonar/php/parser/PHPGrammar.java @@ -40,6 +40,8 @@ import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.DeclaredTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; import org.sonar.plugins.php.api.tree.declaration.EnumDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree; @@ -607,9 +609,31 @@ public IntersectionTypeTree INTERSECTION_TYPE() { b.oneOrMore(f.newTuple(b.token(PHPPunctuator.AMPERSAND), TYPE())))); } + /** + * We don't want to match UNION_TYPE as DNF_TYPE, so we need this more complex grammar to ensure we have a mix of union and intersection: + * DNF_TYPE -> (TYPE "|")* DNF_INTERSECTION_TYPE ("|" (TYPE | DNF_INTERSECTION_TYPE))* + */ + public DnfTypeTree DNF_TYPE() { + return b.nonterminal(PHPLexicalGrammar.DNF_TYPE).is( + b.firstOf( + f.dnfType( + b.zeroOrMore(f.newTuple(TYPE(), b.token(PHPPunctuator.OR))), + DNF_INTERSECTION_TYPE(), + b.zeroOrMore(f.newTuple(b.token(PHPPunctuator.OR), b.firstOf(TYPE(), DNF_INTERSECTION_TYPE())))))); + } + + public DnfIntersectionTypeTree DNF_INTERSECTION_TYPE() { + return b.nonterminal(PHPLexicalGrammar.DNF_INTERSECTION_TYPE).is( + f.dnfIntersectionType( + b.token(PHPPunctuator.LPARENTHESIS), + TYPE(), + b.oneOrMore(f.newTuple(b.token(PHPPunctuator.AMPERSAND), TYPE())), + b.token(PHPPunctuator.RPARENTHESIS))); + } + public DeclaredTypeTree DECLARED_TYPE() { return b.nonterminal(PHPLexicalGrammar.DECLARED_TYPE).is( - b.firstOf(UNION_TYPE(), INTERSECTION_TYPE(), TYPE())); + b.firstOf(DNF_TYPE(), UNION_TYPE(), INTERSECTION_TYPE(), TYPE())); } /** diff --git a/php-frontend/src/main/java/org/sonar/php/parser/PHPLexicalGrammar.java b/php-frontend/src/main/java/org/sonar/php/parser/PHPLexicalGrammar.java index b2af0af770..60fa556e2a 100644 --- a/php-frontend/src/main/java/org/sonar/php/parser/PHPLexicalGrammar.java +++ b/php-frontend/src/main/java/org/sonar/php/parser/PHPLexicalGrammar.java @@ -74,6 +74,8 @@ public enum PHPLexicalGrammar implements GrammarRuleKey { UNION_TYPE, INTERSECTION_TYPE, DECLARED_TYPE, + DNF_TYPE, + DNF_INTERSECTION_TYPE, /** * Lexical diff --git a/php-frontend/src/main/java/org/sonar/php/parser/TreeFactory.java b/php-frontend/src/main/java/org/sonar/php/parser/TreeFactory.java index 95b5a1306c..231c23f1d3 100644 --- a/php-frontend/src/main/java/org/sonar/php/parser/TreeFactory.java +++ b/php-frontend/src/main/java/org/sonar/php/parser/TreeFactory.java @@ -45,6 +45,8 @@ import org.sonar.php.tree.impl.declaration.ClassPropertyDeclarationTreeImpl; import org.sonar.php.tree.impl.declaration.CombinedTypeTreeImpl; import org.sonar.php.tree.impl.declaration.ConstantDeclarationTreeImpl; +import org.sonar.php.tree.impl.declaration.DnfIntersectionTypeTreeImpl; +import org.sonar.php.tree.impl.declaration.DnfTypeTreeImpl; import org.sonar.php.tree.impl.declaration.EnumDeclarationTreeImpl; import org.sonar.php.tree.impl.declaration.FunctionDeclarationTreeImpl; import org.sonar.php.tree.impl.declaration.MethodDeclarationTreeImpl; @@ -151,6 +153,8 @@ import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.DeclaredTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; import org.sonar.plugins.php.api.tree.declaration.EnumDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree; @@ -1872,13 +1876,13 @@ public ExecutionOperatorTree executionOperator(ExpandableStringLiteralTree liter return new ExecutionOperatorTreeImpl(literal); } - private static SeparatedList combinedTypes(TypeTree type1, List> rest) { - List types = new ArrayList<>(); + private static SeparatedList combinedTypes(T firstType, List> rest) { + List types = new ArrayList<>(); List separators = new ArrayList<>(); - types.add(type1); + types.add(firstType); - for (Tuple tuple : rest) { + for (Tuple tuple : rest) { separators.add(tuple.first); types.add(tuple.second); } @@ -1886,12 +1890,37 @@ private static SeparatedList combinedTypes(TypeTree type1, List(types, separators); } - public UnionTypeTree unionType(TypeTree type1, List> rest) { - return new CombinedTypeTreeImpl.UnionTypeTreeImpl(combinedTypes(type1, rest)); + public UnionTypeTree unionType(TypeTree firstType, List> rest) { + return new CombinedTypeTreeImpl.UnionTypeTreeImpl(combinedTypes(firstType, rest)); } - public IntersectionTypeTree intersectionType(TypeTree type1, List> rest) { - return new CombinedTypeTreeImpl.IntersectionTypeTreeImpl(combinedTypes(type1, rest)); + public IntersectionTypeTree intersectionType(TypeTree firstType, List> rest) { + return new CombinedTypeTreeImpl.IntersectionTypeTreeImpl(combinedTypes(firstType, rest)); + } + + public DnfTypeTree dnfType( + Optional>> simpleTypes, + DnfIntersectionTypeTree intersectionTypeTree, + Optional>> rest) { + List types = new ArrayList<>(); + List separators = new ArrayList<>(); + + for (Tuple tuple : simpleTypes.or(Collections.emptyList())) { + types.add(tuple.first); + separators.add(tuple.second); + } + types.add(intersectionTypeTree); + for (Tuple tuple : rest.or(Collections.emptyList())) { + separators.add(tuple.first); + types.add(tuple.second); + } + + var separatedList = new SeparatedListImpl<>(types, separators); + return new DnfTypeTreeImpl(separatedList); + } + + public DnfIntersectionTypeTree dnfIntersectionType(SyntaxToken openParenthesis, TypeTree firstType, List> rest, SyntaxToken closedParenthesis) { + return new DnfIntersectionTypeTreeImpl(openParenthesis, combinedTypes(firstType, rest), closedParenthesis); } public CallArgumentTree functionCallArgument(Optional> optional, ExpressionTree firstOf) { diff --git a/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/DnfIntersectionTypeTreeImpl.java b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/DnfIntersectionTypeTreeImpl.java new file mode 100644 index 0000000000..4d935836c5 --- /dev/null +++ b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/DnfIntersectionTypeTreeImpl.java @@ -0,0 +1,80 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 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.php.tree.impl.declaration; + +import java.util.Iterator; +import org.sonar.php.tree.impl.PHPTree; +import org.sonar.php.utils.collections.IteratorUtils; +import org.sonar.plugins.php.api.tree.SeparatedList; +import org.sonar.plugins.php.api.tree.Tree; +import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree; +import org.sonar.plugins.php.api.tree.declaration.TypeTree; +import org.sonar.plugins.php.api.tree.lexical.SyntaxToken; +import org.sonar.plugins.php.api.visitors.VisitorCheck; + +public class DnfIntersectionTypeTreeImpl extends PHPTree implements DnfIntersectionTypeTree { + private final SyntaxToken openParenthesisToken; + private final SeparatedList types; + private final SyntaxToken closedParenthesisToken; + + public DnfIntersectionTypeTreeImpl(SyntaxToken openParenthesisToken, SeparatedList types, SyntaxToken closedParenthesisToken) { + this.openParenthesisToken = openParenthesisToken; + this.types = types; + this.closedParenthesisToken = closedParenthesisToken; + } + + @Override + public Iterator childrenIterator() { + return IteratorUtils.concat( + IteratorUtils.iteratorOf(openParenthesisToken), + types.elementsAndSeparators(), + IteratorUtils.iteratorOf(closedParenthesisToken)); + } + + @Override + public SyntaxToken openParenthesisToken() { + return openParenthesisToken; + } + + @Override + public SeparatedList types() { + return types; + } + + @Override + public SyntaxToken closedParenthesisToken() { + return closedParenthesisToken; + } + + @Override + public boolean isSimple() { + return false; + } + + @Override + public void accept(VisitorCheck visitor) { + visitor.visitDnfIntersectionType(this); + } + + @Override + public Kind getKind() { + return Kind.DNF_INTERSECTION_TYPE; + } +} diff --git a/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/DnfTypeTreeImpl.java b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/DnfTypeTreeImpl.java new file mode 100644 index 0000000000..41deb06756 --- /dev/null +++ b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/DnfTypeTreeImpl.java @@ -0,0 +1,62 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 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.php.tree.impl.declaration; + +import java.util.Iterator; +import org.sonar.php.tree.impl.PHPTree; +import org.sonar.plugins.php.api.tree.SeparatedList; +import org.sonar.plugins.php.api.tree.Tree; +import org.sonar.plugins.php.api.tree.declaration.DeclaredTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; +import org.sonar.plugins.php.api.visitors.VisitorCheck; + +public class DnfTypeTreeImpl extends PHPTree implements DnfTypeTree { + + private final SeparatedList types; + + public DnfTypeTreeImpl(SeparatedList types) { + this.types = types; + } + + @Override + public Iterator childrenIterator() { + return types.elementsAndSeparators(); + } + + @Override + public boolean isSimple() { + return false; + } + + @Override + public void accept(VisitorCheck visitor) { + visitor.visitDnfType(this); + } + + @Override + public Kind getKind() { + return Kind.DNF_TYPE; + } + + @Override + public SeparatedList types() { + return types; + } +} diff --git a/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/Tree.java b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/Tree.java index e8fa2f95fc..1dafbbe892 100644 --- a/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/Tree.java +++ b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/Tree.java @@ -28,6 +28,8 @@ import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree; +import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; import org.sonar.plugins.php.api.tree.declaration.EnumDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree; @@ -307,6 +309,16 @@ enum Kind implements GrammarRuleKey { */ INTERSECTION_TYPE(IntersectionTypeTree.class), + /** + * {@link DnfTypeTree} + */ + DNF_TYPE(DnfTypeTree.class), + + /** + * {@link DnfIntersectionTypeTree} + */ + DNF_INTERSECTION_TYPE(DnfIntersectionTypeTree.class), + /** * {@link NamespaceNameTree} */ @@ -1000,7 +1012,7 @@ enum Kind implements GrammarRuleKey { STATIC_STATEMENT(StaticStatementTree.class), /** - * {@link SyntaxToken} + * {@link SyntaxTrivia} */ TRIVIA(SyntaxTrivia.class), diff --git a/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/DnfIntersectionTypeTree.java b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/DnfIntersectionTypeTree.java new file mode 100644 index 0000000000..19228257af --- /dev/null +++ b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/DnfIntersectionTypeTree.java @@ -0,0 +1,52 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.php.api.tree.declaration; + +import org.sonar.plugins.php.api.tree.SeparatedList; +import org.sonar.plugins.php.api.tree.lexical.SyntaxToken; + +/** + * Represent intersection in Disjunctive Normal Form (DNF) of types. + *
+ *  {@link DnfTypeTree}
+ * 
+ * + * @since 3.39 + */ +public interface DnfIntersectionTypeTree extends DeclaredTypeTree { + + /** + * The open parenthesis token, e.g., ( in (int|string). + * @return the open parenthesis token + */ + SyntaxToken openParenthesisToken(); + + /** + * The list of elements and separators, e.g., int, | and string in (int|string). + * @return the list of elements and separators + */ + SeparatedList types(); + + /** + * The closed parenthesis token, e.g., ) in (int|string). + * @return the closed parenthesis token + */ + SyntaxToken closedParenthesisToken(); +} diff --git a/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/DnfTypeTree.java b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/DnfTypeTree.java new file mode 100644 index 0000000000..4b5c8e90e7 --- /dev/null +++ b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/DnfTypeTree.java @@ -0,0 +1,36 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.php.api.tree.declaration; + +import org.sonar.plugins.php.api.tree.SeparatedList; + +/** + * DNF Types + * + * @since 3.39 + */ +public interface DnfTypeTree extends DeclaredTypeTree { + + /** + * The list of elements and separators, e.g., int, | and (A&B) in int|(A&B). + * @return the list of elements and separators + */ + SeparatedList types(); +} diff --git a/php-frontend/src/main/java/org/sonar/plugins/php/api/visitors/PHPVisitorCheck.java b/php-frontend/src/main/java/org/sonar/plugins/php/api/visitors/PHPVisitorCheck.java index b85216a42b..f6fe0715ee 100644 --- a/php-frontend/src/main/java/org/sonar/plugins/php/api/visitors/PHPVisitorCheck.java +++ b/php-frontend/src/main/java/org/sonar/plugins/php/api/visitors/PHPVisitorCheck.java @@ -36,6 +36,8 @@ import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree; +import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree; import org.sonar.plugins.php.api.tree.declaration.MethodDeclarationTree; @@ -584,6 +586,16 @@ public void visitIntersectionType(IntersectionTypeTree tree) { scan(tree); } + @Override + public void visitDnfType(DnfTypeTree tree) { + scan(tree); + } + + @Override + public void visitDnfIntersectionType(DnfIntersectionTypeTree tree) { + scan(tree); + } + @Override public void visitBuiltInType(BuiltInTypeTree tree) { scan(tree); diff --git a/php-frontend/src/main/java/org/sonar/plugins/php/api/visitors/VisitorCheck.java b/php-frontend/src/main/java/org/sonar/plugins/php/api/visitors/VisitorCheck.java index 0f30c90c1d..3acccb4b4b 100644 --- a/php-frontend/src/main/java/org/sonar/plugins/php/api/visitors/VisitorCheck.java +++ b/php-frontend/src/main/java/org/sonar/plugins/php/api/visitors/VisitorCheck.java @@ -28,6 +28,8 @@ import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree; +import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree; import org.sonar.plugins.php.api.tree.declaration.MethodDeclarationTree; @@ -158,6 +160,10 @@ public interface VisitorCheck extends PHPCheck { void visitIntersectionType(IntersectionTypeTree tree); + void visitDnfType(DnfTypeTree tree); + + void visitDnfIntersectionType(DnfIntersectionTypeTree tree); + void visitBuiltInType(BuiltInTypeTree tree); void visitReturnTypeClause(ReturnTypeClauseTree tree); diff --git a/php-frontend/src/test/java/org/sonar/php/parser/declaration/ClassMemberTest.java b/php-frontend/src/test/java/org/sonar/php/parser/declaration/ClassMemberTest.java index 61e4ce9794..3e4bf428a1 100644 --- a/php-frontend/src/test/java/org/sonar/php/parser/declaration/ClassMemberTest.java +++ b/php-frontend/src/test/java/org/sonar/php/parser/declaration/ClassMemberTest.java @@ -32,6 +32,7 @@ void test() { .matches("var $a;") .matches("const A;") .matches("private function f() {}") - .matches("public readonly string $prop;"); + .matches("public readonly string $prop;") + .matches("public int|null|(A&B) $a;"); } } diff --git a/php-frontend/src/test/java/org/sonar/php/parser/declaration/DnfInterfactionTypeTest.java b/php-frontend/src/test/java/org/sonar/php/parser/declaration/DnfInterfactionTypeTest.java new file mode 100644 index 0000000000..6afb210af2 --- /dev/null +++ b/php-frontend/src/test/java/org/sonar/php/parser/declaration/DnfInterfactionTypeTest.java @@ -0,0 +1,42 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 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.php.parser.declaration; + +import org.junit.jupiter.api.Test; +import org.sonar.php.parser.PHPLexicalGrammar; + +import static org.sonar.php.utils.Assertions.assertThat; + +class DnfInterfactionTypeTest { + + @Test + void shouldParse() { + assertThat(PHPLexicalGrammar.DNF_INTERSECTION_TYPE) + .matches("(A&B)") + .matches("(A&B&C&D)") + + .notMatches("(A)") + .notMatches("A&B") + .notMatches("(A&B") + .notMatches("((A&B)") + .notMatches("null") + .notMatches("(A&B)|C"); + } +} diff --git a/php-frontend/src/test/java/org/sonar/php/parser/declaration/DnfTypeTest.java b/php-frontend/src/test/java/org/sonar/php/parser/declaration/DnfTypeTest.java new file mode 100644 index 0000000000..24d487fda4 --- /dev/null +++ b/php-frontend/src/test/java/org/sonar/php/parser/declaration/DnfTypeTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 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.php.parser.declaration; + +import org.junit.jupiter.api.Test; +import org.sonar.php.parser.PHPLexicalGrammar; + +import static org.sonar.php.utils.Assertions.assertThat; + +class DnfTypeTest { + + @Test + void shouldParse() { + assertThat(PHPLexicalGrammar.DNF_TYPE) + .matches("(A&B)") + .matches("(A&B)|int") + .matches("int|(A&B)") + .matches("int|null|(A&B)") + .matches("int|(A&B)|null") + .matches("(A&B)|(C&D)") + .matches("(A&B)|C") + + .notMatches("int|null") + .notMatches("int") + .notMatches("A&B") + .notMatches("A&B|null") + .notMatches("A&null") + .notMatches("null"); + } +} diff --git a/php-frontend/src/test/java/org/sonar/php/parser/declaration/FunctionDeclarationTest.java b/php-frontend/src/test/java/org/sonar/php/parser/declaration/FunctionDeclarationTest.java index c08bbc1bf9..efe554ea64 100644 --- a/php-frontend/src/test/java/org/sonar/php/parser/declaration/FunctionDeclarationTest.java +++ b/php-frontend/src/test/java/org/sonar/php/parser/declaration/FunctionDeclarationTest.java @@ -40,6 +40,8 @@ void test() { .matches("function f($prop = new Foo()) {}") .matches("function f(A&B $prop): A&B {}") .matches("function f(A|B $prop): A|B {}") + .matches("function f(null|(A&B) $prop): void {}") + .matches("function f(A $prop): null|(A&B) {}") // readonly is a keyword, but it can be used as a function name .matches("function readonly() {}") .matches("function READONLY() {}") diff --git a/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/DnfTypeTreeTest.java b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/DnfTypeTreeTest.java new file mode 100644 index 0000000000..acfa764f62 --- /dev/null +++ b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/DnfTypeTreeTest.java @@ -0,0 +1,85 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 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.php.tree.impl.declaration; + +import org.junit.jupiter.api.Test; +import org.sonar.php.PHPTreeModelTest; +import org.sonar.php.parser.PHPLexicalGrammar; +import org.sonar.plugins.php.api.tree.Tree; +import org.sonar.plugins.php.api.tree.declaration.BuiltInTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; +import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree; +import org.sonar.plugins.php.api.tree.declaration.TypeTree; +import org.sonar.plugins.php.api.tree.lexical.SyntaxToken; + +import static org.assertj.core.api.Assertions.assertThat; + +class DnfTypeTreeTest extends PHPTreeModelTest { + + @Test + void shouldParseDnfType() { + DnfTypeTree dnfType = parse("int|null|(A&B)", PHPLexicalGrammar.DNF_TYPE); + assertThat(dnfType.isSimple()).isFalse(); + assertThat(dnfType.types()).hasSize(3); + var elementsAndSeparators = dnfType.types().elementsAndSeparators(); + + // Checking individual elements of int|null|(A&B) + var element1 = (TypeTree) elementsAndSeparators.next(); + assertThat(element1.is(Tree.Kind.TYPE)).isTrue(); + assertThat(element1.typeName().is(Tree.Kind.BUILT_IN_TYPE)).isTrue(); + assertThat(((BuiltInTypeTree) element1.typeName()).token().text()).isEqualTo("int"); + + var separator1 = (SyntaxToken) elementsAndSeparators.next(); + assertThat(separator1.is(Tree.Kind.TOKEN)).isTrue(); + assertThat(separator1.text()).isEqualTo("|"); + + var element2 = (TypeTree) elementsAndSeparators.next(); + assertThat(element2.is(Tree.Kind.TYPE)).isTrue(); + assertThat(element2.typeName().is(Tree.Kind.NAMESPACE_NAME)).isTrue(); + assertThat(((NamespaceNameTree) element2.typeName()).name().text()).isEqualTo("null"); + + var separator2 = (SyntaxToken) elementsAndSeparators.next(); + assertThat(separator2.is(Tree.Kind.TOKEN)).isTrue(); + assertThat(separator2.text()).isEqualTo("|"); + + var element3 = (DnfIntersectionTypeTree) elementsAndSeparators.next(); + assertThat(element3.is(Tree.Kind.DNF_INTERSECTION_TYPE)).isTrue(); + assertThat(element3.isSimple()).isFalse(); + assertThat(element3.openParenthesisToken().text()).isEqualTo("("); + assertThat(element3.types()).hasSize(2); + assertThat(element3.closedParenthesisToken().text()).isEqualTo(")"); + + // Checking individual elements of A&B + var dnfIntersectionElements = element3.types().elementsAndSeparators(); + + var dnfIntersectionElement1 = (TypeTree) dnfIntersectionElements.next(); + assertThat(dnfIntersectionElement1.typeName().is(Tree.Kind.NAMESPACE_NAME)).isTrue(); + assertThat(((NamespaceNameTree) dnfIntersectionElement1.typeName()).name().text()).isEqualTo("A"); + + var dnfIntersectionSeparator = (SyntaxToken) dnfIntersectionElements.next(); + assertThat(dnfIntersectionSeparator.is(Tree.Kind.TOKEN)).isTrue(); + assertThat(dnfIntersectionSeparator.text()).isEqualTo("&"); + + var dnfIntersectionElement2 = (TypeTree) dnfIntersectionElements.next(); + assertThat(dnfIntersectionElement2.typeName().is(Tree.Kind.NAMESPACE_NAME)).isTrue(); + assertThat(((NamespaceNameTree) dnfIntersectionElement2.typeName()).name().text()).isEqualTo("B"); + } +} diff --git a/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/FunctionDeclarationTreeTest.java b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/FunctionDeclarationTreeTest.java index e3e13cc311..8920426064 100644 --- a/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/FunctionDeclarationTreeTest.java +++ b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/FunctionDeclarationTreeTest.java @@ -24,6 +24,7 @@ import org.sonar.php.PHPTreeModelTest; import org.sonar.php.parser.PHPLexicalGrammar; import org.sonar.plugins.php.api.tree.Tree.Kind; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.TypeTree; import org.sonar.plugins.php.api.tree.declaration.UnionTypeTree; @@ -34,7 +35,7 @@ class FunctionDeclarationTreeTest extends PHPTreeModelTest { @Test - void simpleDeclaration() { + void shouldSupportSimpleDeclaration() { FunctionDeclarationTree tree = parse("function f($p) {}", PHPLexicalGrammar.FUNCTION_DECLARATION); assertThat(tree.is(Kind.FUNCTION_DECLARATION)).isTrue(); assertThat(tree.attributeGroups()).isEmpty(); @@ -47,13 +48,13 @@ void simpleDeclaration() { } @Test - void reference() { + void shouldSupportReference() { FunctionDeclarationTree tree = parse("function &f($p) {}", PHPLexicalGrammar.FUNCTION_DECLARATION); assertThat(tree.referenceToken()).isNotNull(); } @Test - void withReturnTypeClause() { + void shouldSupportReturnTypeClause() { FunctionDeclarationTree tree = parse("function f() : array {}", PHPLexicalGrammar.FUNCTION_DECLARATION); assertThat(tree.returnTypeClause()).isNotNull(); assertThat(tree.returnTypeClause().colonToken().text()).isEqualTo(":"); @@ -63,7 +64,7 @@ void withReturnTypeClause() { } @Test - void withUnionReturnTypeClause() { + void shouldSupportUnionReturnTypeClause() { FunctionDeclarationTree tree = parse("function f() : array|int {}", PHPLexicalGrammar.FUNCTION_DECLARATION); assertThat(tree.returnTypeClause()).isNotNull(); assertThat(tree.returnTypeClause().colonToken().text()).isEqualTo(":"); @@ -73,14 +74,25 @@ void withUnionReturnTypeClause() { } @Test - void withAttributes() { + void shouldSupportAttributes() { FunctionDeclarationTree tree = parse("#[A1(8), A2] function f() {}", PHPLexicalGrammar.FUNCTION_DECLARATION); assertThat(tree.attributeGroups()).hasSize(1); assertThat(tree.attributeGroups().get(0).attributes()).hasSize(2); } @Test - void parameterWithVisibilityModifier() { + void shouldSupportParameterWithVisibilityModifier() { assertThatExceptionOfType(RecognitionException.class).isThrownBy(() -> parse("function f(public $p) {}", PHPLexicalGrammar.FUNCTION_DECLARATION)); } + + @Test + void shouldSupportDnfTypeInParameter() { + FunctionDeclarationTree tree = parse("function f(int|null|(A&B) $p) {}", PHPLexicalGrammar.FUNCTION_DECLARATION); + assertThat(tree.parameters().parameters()).hasSize(1); + var type = tree.parameters().parameters().get(0).declaredType(); + assertThat(type.getKind()).isEqualTo(Kind.DNF_TYPE); + var dnfType = (DnfTypeTree) type; + assertThat(dnfType.isSimple()).isFalse(); + assertThat(dnfType.types()).hasSize(3); + } } diff --git a/php-frontend/src/test/java/org/sonar/php/tree/visitors/PHPSubscriptionCheckTest.java b/php-frontend/src/test/java/org/sonar/php/tree/visitors/PHPSubscriptionCheckTest.java index 00269cd8d2..47ab457168 100644 --- a/php-frontend/src/test/java/org/sonar/php/tree/visitors/PHPSubscriptionCheckTest.java +++ b/php-frontend/src/test/java/org/sonar/php/tree/visitors/PHPSubscriptionCheckTest.java @@ -49,8 +49,8 @@ void test() { testVisitor.analyze(file, tree); assertThat(testVisitor.classCounter).isEqualTo(1); - assertThat(testVisitor.namespaceNameCounter).isEqualTo(7); - assertThat(testVisitor.varIdentifierCounter).isEqualTo(8); + assertThat(testVisitor.namespaceNameCounter).isEqualTo(9); + assertThat(testVisitor.varIdentifierCounter).isEqualTo(9); } @Test diff --git a/php-frontend/src/test/java/org/sonar/php/tree/visitors/PHPVisitorCheckTest.java b/php-frontend/src/test/java/org/sonar/php/tree/visitors/PHPVisitorCheckTest.java index f9922c20da..4cc90c9e7b 100644 --- a/php-frontend/src/test/java/org/sonar/php/tree/visitors/PHPVisitorCheckTest.java +++ b/php-frontend/src/test/java/org/sonar/php/tree/visitors/PHPVisitorCheckTest.java @@ -34,6 +34,8 @@ import org.sonar.plugins.php.api.tree.declaration.AttributeGroupTree; import org.sonar.plugins.php.api.tree.declaration.AttributeTree; import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree; +import org.sonar.plugins.php.api.tree.declaration.DnfIntersectionTypeTree; +import org.sonar.plugins.php.api.tree.declaration.DnfTypeTree; import org.sonar.plugins.php.api.tree.declaration.IntersectionTypeTree; import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree; import org.sonar.plugins.php.api.tree.declaration.UnionTypeTree; @@ -79,15 +81,17 @@ void shouldVisitTreeElements() { testVisitor.analyze(file, tree); assertThat(testVisitor.classCounter).isEqualTo(2); - assertThat(testVisitor.namespaceNameCounter).isEqualTo(7); - assertThat(testVisitor.varIdentifierCounter).isEqualTo(8); + assertThat(testVisitor.namespaceNameCounter).isEqualTo(9); + assertThat(testVisitor.varIdentifierCounter).isEqualTo(9); // PHPCheck#init() is called by PHPAnalyzer assertThat(testVisitor.initCounter).isZero(); assertThat(testVisitor.literalCounter).isEqualTo(6); - assertThat(testVisitor.tokenCounter).isEqualTo(96); + assertThat(testVisitor.tokenCounter).isEqualTo(105); assertThat(testVisitor.triviaCounter).isEqualTo(2); assertThat(testVisitor.unionTypesCounter).isEqualTo(1); assertThat(testVisitor.intersectionTypeCounter).isEqualTo(1); + assertThat(testVisitor.dnfTypeCounter).isEqualTo(1); + assertThat(testVisitor.dnfIntersectionTypeCounter).isEqualTo(1); assertThat(testVisitor.attributeGroupsCounter).isEqualTo(2); assertThat(testVisitor.attributesCounter).isEqualTo(3); assertThat(testVisitor.enumsCounter).isEqualTo(1); @@ -205,6 +209,8 @@ private class TestVisitor extends PHPVisitorCheck { int enumCasesCounter = 0; int callableConvertCounter = 0; int intersectionTypeCounter = 0; + int dnfTypeCounter = 0; + int dnfIntersectionTypeCounter = 0; @Override public void visitClassDeclaration(ClassDeclarationTree tree) { @@ -251,6 +257,18 @@ public void visitIntersectionType(IntersectionTypeTree tree) { intersectionTypeCounter++; } + @Override + public void visitDnfType(DnfTypeTree tree) { + super.visitDnfType(tree); + dnfTypeCounter++; + } + + @Override + public void visitDnfIntersectionType(DnfIntersectionTypeTree tree) { + super.visitDnfIntersectionType(tree); + dnfIntersectionTypeCounter++; + } + @Override public void visitAttributeGroup(AttributeGroupTree tree) { super.visitAttributeGroup(tree); diff --git a/php-frontend/src/test/resources/visitors/test.php b/php-frontend/src/test/resources/visitors/test.php index 989c69ae0b..f5ef3ecc69 100644 --- a/php-frontend/src/test/resources/visitors/test.php +++ b/php-frontend/src/test/resources/visitors/test.php @@ -6,7 +6,7 @@ class A extends B { public $field; /* comment 1 */ // comment 2 - public function foo(#[A2, A3,] int|array $a) { + public function foo(#[A2, A3,] int|array $a, int|(A&B) $b) { $var = 1; foo(1, 2); `cat $var`;