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 7068804778..381f15bcf5 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 @@ -49,6 +49,8 @@ import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree; import org.sonar.plugins.php.api.tree.declaration.ParameterListTree; import org.sonar.plugins.php.api.tree.declaration.ParameterTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookListTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookTree; import org.sonar.plugins.php.api.tree.declaration.ReturnTypeClauseTree; import org.sonar.plugins.php.api.tree.declaration.TypeNameTree; import org.sonar.plugins.php.api.tree.declaration.TypeTree; @@ -342,7 +344,7 @@ public EnumDeclarationTree ENUM_DECLARATION() { } /** - * In contrast to class declarations, enums cannot contain properties. They do allow enum cases as an addition. + * In contrast to class declarations, enums cannot contain properties. They do allow enum cases as an addition. */ public ClassMemberTree ENUM_MEMBER() { return b.nonterminal(PHPLexicalGrammar.ENUM_MEMBER).is( @@ -410,15 +412,24 @@ public ConstantDeclarationTree CONSTANT_DECLARATION() { public ClassPropertyDeclarationTree CLASS_VARIABLE_DECLARATION() { return b.nonterminal(PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION).is( - f.classVariableDeclaration( - b.zeroOrMore(ATTRIBUTE_GROUP()), - b.firstOf( - f.singleToken(b.token(PHPKeyword.VAR)), - b.oneOrMore(MEMBER_MODIFIER())), - b.optional(DECLARED_TYPE()), - VARIABLE_DECLARATION(), - b.zeroOrMore(f.newTuple(b.token(COMMA), VARIABLE_DECLARATION())), - EOS())); + b.firstOf( + f.classVariableDeclaration( + b.zeroOrMore(ATTRIBUTE_GROUP()), + b.firstOf( + f.singleToken(b.token(PHPKeyword.VAR)), + b.oneOrMore(MEMBER_MODIFIER())), + b.optional(DECLARED_TYPE()), + VARIABLE_DECLARATION(), + PROPERTY_HOOK_LIST()), + f.classVariableDeclaration( + b.zeroOrMore(ATTRIBUTE_GROUP()), + b.firstOf( + f.singleToken(b.token(PHPKeyword.VAR)), + b.oneOrMore(MEMBER_MODIFIER())), + b.optional(DECLARED_TYPE()), + VARIABLE_DECLARATION(), + b.zeroOrMore(f.newTuple(b.token(COMMA), VARIABLE_DECLARATION())), + EOS()))); } public SyntaxToken VISIBILITY_MODIFIER() { @@ -636,6 +647,28 @@ public DeclaredTypeTree DECLARED_TYPE() { b.firstOf(DNF_TYPE(), UNION_TYPE(), INTERSECTION_TYPE(), TYPE())); } + public PropertyHookListTree PROPERTY_HOOK_LIST() { + return b.nonterminal(PHPLexicalGrammar.PROPERTY_HOOK_LIST).is( + f.propertyHookList( + b.token(LCURLYBRACE), + b.oneOrMore(PROPERTY_HOOK()), + b.token(RCURLYBRACE))); + } + + public PropertyHookTree PROPERTY_HOOK() { + return b.nonterminal(PHPLexicalGrammar.PROPERTY_HOOK).is( + f.propertyHook( + b.zeroOrMore(ATTRIBUTE_GROUP()), + b.zeroOrMore(MEMBER_MODIFIER()), + b.optional(b.token(PHPPunctuator.AMPERSAND)), + NAME_IDENTIFIER_OR_KEYWORD(), + b.optional(PARAMETER_LIST()), + b.optional(b.token(DOUBLEARROW)), + b.firstOf( + EOS(), + INNER_STATEMENT()))); + } + /** * [ END ] Declaration */ 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 60fa556e2a..e16a77f903 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 @@ -63,6 +63,9 @@ public enum PHPLexicalGrammar implements GrammarRuleKey { MEMBER_CONST_DECLARATION, FUNCTION_CALL_ARGUMENT, + PROPERTY_HOOK_LIST, + PROPERTY_HOOK, + TRAIT_METHOD_REFERENCE_FULLY_QUALIFIED, TRAIT_METHOD_REFERENCE, TRAIT_ALIAS, 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 231c23f1d3..fc532db6ab 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 @@ -53,6 +53,8 @@ import org.sonar.php.tree.impl.declaration.NamespaceNameTreeImpl; import org.sonar.php.tree.impl.declaration.ParameterListTreeImpl; import org.sonar.php.tree.impl.declaration.ParameterTreeImpl; +import org.sonar.php.tree.impl.declaration.PropertyHookListTreeImpl; +import org.sonar.php.tree.impl.declaration.PropertyHookTreeImpl; import org.sonar.php.tree.impl.declaration.ReturnTypeClauseTreeImpl; import org.sonar.php.tree.impl.declaration.TraitAliasTreeImpl; import org.sonar.php.tree.impl.declaration.TraitMethodReferenceTreeImpl; @@ -162,6 +164,8 @@ import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree; import org.sonar.plugins.php.api.tree.declaration.ParameterListTree; import org.sonar.plugins.php.api.tree.declaration.ParameterTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookListTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookTree; import org.sonar.plugins.php.api.tree.declaration.ReturnTypeClauseTree; import org.sonar.plugins.php.api.tree.declaration.TypeNameTree; import org.sonar.plugins.php.api.tree.declaration.TypeTree; @@ -477,9 +481,24 @@ public ClassPropertyDeclarationTree classVariableDeclaration( modifierTokens, typeAnnotation.orNull(), separatedList(firstVariable, additionalVariables), + null, eosToken); } + public ClassPropertyDeclarationTree classVariableDeclaration( + Optional> attributes, + List modifierTokens, + Optional typeAnnotation, + VariableDeclarationTree variable, + PropertyHookListTree propertyHookListTree) { + return ClassPropertyDeclarationTreeImpl.variable(attributes.or(Collections.emptyList()), + modifierTokens, + typeAnnotation.orNull(), + separatedList(variable, Optional.absent()), + propertyHookListTree, + null); + } + public MethodDeclarationTree methodDeclaration( Optional> attributes, Optional> modifiers, @@ -681,6 +700,38 @@ public EnumCaseTree enumCase(Optional> attributes, Synt return new EnumCaseTreeImpl(attributes.or(Collections.emptyList()), caseToken, name, equalToken, value, eosToken); } + public PropertyHookListTree propertyHookList( + InternalSyntaxToken openCurlyBrace, + List hooks, + InternalSyntaxToken closeCurlyBrace) { + return new PropertyHookListTreeImpl(openCurlyBrace, hooks, closeCurlyBrace); + } + + public PropertyHookTree propertyHook( + Optional> attributes, + Optional> modifiers, + Optional referenceToken, + NameIdentifierTree name, + Optional parameters, + Optional doubleArrowToken, + Tree body) { + throwOnUnrecognizedPropertyHookName(name); + return new PropertyHookTreeImpl( + attributes.or(Collections.emptyList()), + optionalList(modifiers), + referenceToken.orNull(), + name, + parameters.orNull(), + doubleArrowToken.orNull(), + body); + } + + private static void throwOnUnrecognizedPropertyHookName(NameIdentifierTree name) { + if (!"get".equals(name.text()) && !"set".equals(name.text())) { + throw new RecognitionException(((PHPTree) name).getLine(), "Declared property hook must be named \"get\" or \"set\""); + } + } + /** * [ END ] Declarations */ diff --git a/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/ClassPropertyDeclarationTreeImpl.java b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/ClassPropertyDeclarationTreeImpl.java index dcc41335c6..f212fbbee9 100644 --- a/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/ClassPropertyDeclarationTreeImpl.java +++ b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/ClassPropertyDeclarationTreeImpl.java @@ -32,6 +32,7 @@ import org.sonar.plugins.php.api.tree.declaration.AttributeGroupTree; import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree; import org.sonar.plugins.php.api.tree.declaration.DeclaredTypeTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookListTree; import org.sonar.plugins.php.api.tree.declaration.TypeTree; import org.sonar.plugins.php.api.tree.declaration.UnionTypeTree; import org.sonar.plugins.php.api.tree.declaration.VariableDeclarationTree; @@ -44,6 +45,9 @@ public class ClassPropertyDeclarationTreeImpl extends PHPTree implements ClassPr private final List attributeGroups; private final List modifierTokens; private final SeparatedListImpl declarations; + @Nullable + private final PropertyHookListTree propertyHooks; + @Nullable private final InternalSyntaxToken eosToken; @Nullable private final DeclaredTypeTree typeAnnotation; @@ -54,12 +58,14 @@ private ClassPropertyDeclarationTreeImpl( List modifierTokens, @Nullable DeclaredTypeTree typeAnnotation, SeparatedListImpl declarations, - InternalSyntaxToken eosToken) { + @Nullable PropertyHookListTree propertyHooks, + @Nullable InternalSyntaxToken eosToken) { this.kind = kind; this.attributeGroups = attributeGroups; this.modifierTokens = modifierTokens; this.typeAnnotation = typeAnnotation; this.declarations = declarations; + this.propertyHooks = propertyHooks; this.eosToken = eosToken; } @@ -67,12 +73,14 @@ public static ClassPropertyDeclarationTree variable(List att List modifierTokens, @Nullable DeclaredTypeTree typeAnnotation, SeparatedListImpl declarations, - InternalSyntaxToken eosToken) { + @Nullable PropertyHookListTree propertyHook, + @Nullable InternalSyntaxToken eosToken) { return new ClassPropertyDeclarationTreeImpl(Kind.CLASS_PROPERTY_DECLARATION, attributes, Collections.unmodifiableList(modifierTokens), typeAnnotation, declarations, + propertyHook, eosToken); } @@ -90,6 +98,7 @@ public static ClassPropertyDeclarationTree constant(List att Collections.unmodifiableList(modifierTokens), typeAnnotation, declarations, + null, eosToken); } @@ -132,6 +141,13 @@ public SeparatedListImpl declarations() { return declarations; } + @Nullable + @Override + public PropertyHookListTree propertyHooks() { + return propertyHooks; + } + + @Nullable @Override public SyntaxToken eosToken() { return eosToken; @@ -167,13 +183,10 @@ public Iterator childrenIterator() { return IteratorUtils.concat( attributeGroups.iterator(), modifierTokens.iterator(), - nullableIterator(typeAnnotation), + IteratorUtils.nullableIterator(typeAnnotation), declarations.elementsAndSeparators(), - IteratorUtils.iteratorOf(eosToken)); - } - - private static Iterator nullableIterator(@Nullable Tree tree) { - return tree == null ? Collections.emptyIterator() : IteratorUtils.iteratorOf(tree); + IteratorUtils.nullableIterator(propertyHooks), + IteratorUtils.nullableIterator(eosToken)); } @Override diff --git a/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/PropertyHookListTreeImpl.java b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/PropertyHookListTreeImpl.java new file mode 100644 index 0000000000..f385110158 --- /dev/null +++ b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/PropertyHookListTreeImpl.java @@ -0,0 +1,82 @@ +/* + * 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 java.util.List; +import javax.annotation.Nullable; +import org.sonar.php.tree.impl.PHPTree; +import org.sonar.php.tree.impl.lexical.InternalSyntaxToken; +import org.sonar.php.utils.collections.IteratorUtils; +import org.sonar.plugins.php.api.tree.Tree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookListTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookTree; +import org.sonar.plugins.php.api.visitors.VisitorCheck; + +public class PropertyHookListTreeImpl extends PHPTree implements PropertyHookListTree { + + private static final Kind KIND = Kind.PROPERTY_HOOK_LIST; + + private final InternalSyntaxToken openCurlyBrace; + private final List hooks; + private final InternalSyntaxToken closeCurlyBrace; + + public PropertyHookListTreeImpl( + InternalSyntaxToken openCurlyBrace, List hooks, InternalSyntaxToken closeCurlyBrace) { + this.openCurlyBrace = openCurlyBrace; + this.hooks = hooks; + this.closeCurlyBrace = closeCurlyBrace; + } + + @Override + public List hooks() { + return hooks; + } + + @Override + public Iterator childrenIterator() { + return IteratorUtils.concat( + IteratorUtils.iteratorOf(openCurlyBrace), + hooks.iterator(), + IteratorUtils.iteratorOf(closeCurlyBrace)); + } + + @Override + public void accept(VisitorCheck visitor) { + visitor.visitPropertyHookList(this); + } + + @Override + public Kind getKind() { + return KIND; + } + + @Nullable + @Override + public InternalSyntaxToken openCurlyBrace() { + return openCurlyBrace; + } + + @Nullable + @Override + public InternalSyntaxToken closeCurlyBrace() { + return closeCurlyBrace; + } +} diff --git a/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/PropertyHookTreeImpl.java b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/PropertyHookTreeImpl.java new file mode 100644 index 0000000000..2f785c7ab9 --- /dev/null +++ b/php-frontend/src/main/java/org/sonar/php/tree/impl/declaration/PropertyHookTreeImpl.java @@ -0,0 +1,125 @@ +/* + * 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 java.util.List; +import javax.annotation.Nullable; +import org.sonar.php.tree.impl.PHPTree; +import org.sonar.php.tree.impl.lexical.InternalSyntaxToken; +import org.sonar.php.utils.collections.IteratorUtils; +import org.sonar.plugins.php.api.tree.Tree; +import org.sonar.plugins.php.api.tree.declaration.AttributeGroupTree; +import org.sonar.plugins.php.api.tree.declaration.ParameterListTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookTree; +import org.sonar.plugins.php.api.tree.expression.NameIdentifierTree; +import org.sonar.plugins.php.api.tree.lexical.SyntaxToken; +import org.sonar.plugins.php.api.visitors.VisitorCheck; + +public class PropertyHookTreeImpl extends PHPTree implements PropertyHookTree { + + private static final Kind KIND = Kind.PROPERTY_HOOK_METHOD_DECLARATION; + + private final List attributeGroups; + private final List modifiersToken; + private final InternalSyntaxToken referenceToken; + private final NameIdentifierTree name; + @Nullable + private final ParameterListTree parameters; + @Nullable + private final InternalSyntaxToken doubleArrowToken; + private final Tree body; + + public PropertyHookTreeImpl( + List attributeGroups, + List modifiersToken, + @Nullable InternalSyntaxToken referenceToken, + NameIdentifierTree name, + @Nullable ParameterListTree parameters, + @Nullable InternalSyntaxToken doubleArrowToken, + Tree body) { + this.attributeGroups = attributeGroups; + this.modifiersToken = modifiersToken; + this.referenceToken = referenceToken; + this.name = name; + this.parameters = parameters; + this.doubleArrowToken = doubleArrowToken; + this.body = body; + } + + @Override + public List modifiers() { + return modifiersToken; + } + + @Override + public List attributeGroups() { + return attributeGroups; + } + + @Nullable + @Override + public SyntaxToken referenceToken() { + return referenceToken; + } + + @Override + public NameIdentifierTree name() { + return name; + } + + @Override + public ParameterListTree parameters() { + return parameters; + } + + @Nullable + @Override + public SyntaxToken doubleArrowToken() { + return doubleArrowToken; + } + + @Override + public Tree body() { + return body; + } + + @Override + public Kind getKind() { + return KIND; + } + + @Override + public Iterator childrenIterator() { + return IteratorUtils.concat( + attributeGroups.iterator(), + modifiersToken.iterator(), + IteratorUtils.iteratorOf(referenceToken, name), + IteratorUtils.nullableIterator(parameters), + IteratorUtils.nullableIterator(doubleArrowToken), + IteratorUtils.iteratorOf(body)); + } + + @Override + public void accept(VisitorCheck visitor) { + visitor.visitPropertyHook(this); + } + +} diff --git a/php-frontend/src/main/java/org/sonar/php/utils/collections/IteratorUtils.java b/php-frontend/src/main/java/org/sonar/php/utils/collections/IteratorUtils.java index 248273fe35..bbf4eebac2 100644 --- a/php-frontend/src/main/java/org/sonar/php/utils/collections/IteratorUtils.java +++ b/php-frontend/src/main/java/org/sonar/php/utils/collections/IteratorUtils.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import javax.annotation.Nullable; public class IteratorUtils { @@ -39,6 +40,13 @@ public static Iterator iteratorOf(T... element) { return Arrays.asList(element).iterator(); } + public static Iterator nullableIterator(@Nullable T element) { + if (element == null) { + return Collections.emptyIterator(); + } + return iteratorOf(element); + } + @SafeVarargs public static Iterator concat(Iterator... iterators) { return new IteratorIterator<>(iterators); 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 1dafbbe892..64d1234d74 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 @@ -37,6 +37,8 @@ import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree; import org.sonar.plugins.php.api.tree.declaration.ParameterListTree; import org.sonar.plugins.php.api.tree.declaration.ParameterTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookListTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookTree; import org.sonar.plugins.php.api.tree.declaration.ReturnTypeClauseTree; import org.sonar.plugins.php.api.tree.declaration.TypeTree; import org.sonar.plugins.php.api.tree.declaration.UnionTypeTree; @@ -181,6 +183,11 @@ enum Kind implements GrammarRuleKey { */ METHOD_DECLARATION(MethodDeclarationTree.class), + /** + * {@link PropertyHookTree} + */ + PROPERTY_HOOK_METHOD_DECLARATION(PropertyHookTree.class), + /** * {@link FunctionDeclarationTree} */ @@ -211,6 +218,11 @@ enum Kind implements GrammarRuleKey { */ VARIABLE_DECLARATION(VariableDeclarationTree.class), + /** + * {@link PropertyHookListTree} + */ + PROPERTY_HOOK_LIST(PropertyHookListTree.class), + /** * {@link UseClauseTree} */ diff --git a/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/ClassPropertyDeclarationTree.java b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/ClassPropertyDeclarationTree.java index 2f0d34c680..4f279c97dd 100644 --- a/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/ClassPropertyDeclarationTree.java +++ b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/ClassPropertyDeclarationTree.java @@ -57,6 +57,13 @@ public interface ClassPropertyDeclarationTree extends ClassMemberTree, HasAttrib SeparatedList declarations(); + /** + * {@link PropertyHookListTree} + */ + @Nullable + PropertyHookListTree propertyHooks(); + + @Nullable SyntaxToken eosToken(); boolean hasModifiers(String... modifiers); diff --git a/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/PropertyHookListTree.java b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/PropertyHookListTree.java new file mode 100644 index 0000000000..9f6c545fe9 --- /dev/null +++ b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/PropertyHookListTree.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.plugins.php.api.tree.declaration; + +import java.util.List; +import org.sonar.php.tree.impl.lexical.InternalSyntaxToken; +import org.sonar.plugins.php.api.tree.Tree; + +/** + * Property Hooks + * + * @since 3.39 + */ +public interface PropertyHookListTree extends Tree { + + /** + * The open curly brace beginning the declaration of property hooks. + */ + InternalSyntaxToken openCurlyBrace(); + + /** + * The list of {@link PropertyHookTree property hooks} + */ + List hooks(); + + /** + * The closed curly brace ending the declaration of property hooks. + */ + InternalSyntaxToken closeCurlyBrace(); +} diff --git a/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/PropertyHookTree.java b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/PropertyHookTree.java new file mode 100644 index 0000000000..23f82d8aa4 --- /dev/null +++ b/php-frontend/src/main/java/org/sonar/plugins/php/api/tree/declaration/PropertyHookTree.java @@ -0,0 +1,54 @@ +/* + * 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 java.util.List; +import javax.annotation.Nullable; +import org.sonar.php.api.PHPPunctuator; +import org.sonar.plugins.php.api.tree.Tree; +import org.sonar.plugins.php.api.tree.expression.NameIdentifierTree; +import org.sonar.plugins.php.api.tree.lexical.SyntaxToken; + +/** + * Property Hooks + * + * @since 3.39 + */ +public interface PropertyHookTree extends Tree, HasAttributes { + + List modifiers(); + + @Nullable + SyntaxToken doubleArrowToken(); + + @Nullable + SyntaxToken referenceToken(); + + NameIdentifierTree name(); + + @Nullable + ParameterListTree parameters(); + + /** + * Either {@link PHPPunctuator#SEMICOLON ;} or {@link Kind#BLOCK block} + */ + Tree body(); + +} 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 f6fe0715ee..3a33fabf74 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 @@ -44,6 +44,8 @@ import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree; import org.sonar.plugins.php.api.tree.declaration.ParameterListTree; import org.sonar.plugins.php.api.tree.declaration.ParameterTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookListTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookTree; import org.sonar.plugins.php.api.tree.declaration.ReturnTypeClauseTree; import org.sonar.plugins.php.api.tree.declaration.TypeTree; import org.sonar.plugins.php.api.tree.declaration.UnionTypeTree; @@ -606,6 +608,16 @@ public void visitReturnTypeClause(ReturnTypeClauseTree tree) { scan(tree); } + @Override + public void visitPropertyHookList(PropertyHookListTree tree) { + scan(tree); + } + + @Override + public void visitPropertyHook(PropertyHookTree tree) { + scan(tree); + } + @Override public void visitHeredoc(HeredocStringLiteralTree 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 3acccb4b4b..eb8fb2745d 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 @@ -36,6 +36,8 @@ import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree; import org.sonar.plugins.php.api.tree.declaration.ParameterListTree; import org.sonar.plugins.php.api.tree.declaration.ParameterTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookListTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookTree; import org.sonar.plugins.php.api.tree.declaration.ReturnTypeClauseTree; import org.sonar.plugins.php.api.tree.declaration.TypeTree; import org.sonar.plugins.php.api.tree.declaration.UnionTypeTree; @@ -168,6 +170,10 @@ public interface VisitorCheck extends PHPCheck { void visitReturnTypeClause(ReturnTypeClauseTree tree); + void visitPropertyHookList(PropertyHookListTree tree); + + void visitPropertyHook(PropertyHookTree tree); + /** * [ END ] Declaration */ diff --git a/php-frontend/src/test/java/org/sonar/php/parser/PHPParserTest.java b/php-frontend/src/test/java/org/sonar/php/parser/PHPParserTest.java index 9f98e851aa..8580416252 100644 --- a/php-frontend/src/test/java/org/sonar/php/parser/PHPParserTest.java +++ b/php-frontend/src/test/java/org/sonar/php/parser/PHPParserTest.java @@ -47,4 +47,5 @@ void forEach() { ForEachStatementTree tree = (ForEachStatementTree) parser.parse("foreach ($arr as &$value) { }"); Assertions.assertThat(tree.expression().getParent()).isSameAs(tree); } + } diff --git a/php-frontend/src/test/java/org/sonar/php/parser/declaration/PropertyHookListTest.java b/php-frontend/src/test/java/org/sonar/php/parser/declaration/PropertyHookListTest.java new file mode 100644 index 0000000000..065778b871 --- /dev/null +++ b/php-frontend/src/test/java/org/sonar/php/parser/declaration/PropertyHookListTest.java @@ -0,0 +1,51 @@ +/* + * 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 PropertyHookListTest { + + @Test + void shouldMatchPropertyHookList() { + assertThat(PHPLexicalGrammar.PROPERTY_HOOK_LIST) + .matches("{ set; }") + .matches("{ get => 123; }") + .matches("{ get; set; }") + .matches("{ get {} set; }") + .matches("{ set; get {} }") + .matches("{ &get; }") + .matches("{ get { return implode(', ', array_map(fn (Author $author) => $author->name,$this->authors,)); } }") + + .matches("{ set; set; set; }") + + .notMatches("{ name; } ") + .notMatches("{ name;") + .notMatches("name; } ") + .notMatches("{ get; name; } ") + .notMatches("{ name; set; } ") + .notMatches("{ name => 123; }") + .notMatches("get;"); + } + +} diff --git a/php-frontend/src/test/java/org/sonar/php/parser/declaration/PropertyHookTest.java b/php-frontend/src/test/java/org/sonar/php/parser/declaration/PropertyHookTest.java new file mode 100644 index 0000000000..a7c43777fb --- /dev/null +++ b/php-frontend/src/test/java/org/sonar/php/parser/declaration/PropertyHookTest.java @@ -0,0 +1,54 @@ +/* + * 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 PropertyHookTest { + + @Test + void shouldMatchPropertyHooks() { + assertThat(PHPLexicalGrammar.PROPERTY_HOOK) + .matches("get;") + .matches("set;") + .matches("#[A1(4)] get;") + .matches("public get;") + .matches("final get;") + .matches("&get;") + .matches("&get => $this->a;") + .matches("set => 123;") + .matches("get => $this->a;") + .matches("get {}") + .matches("get { return 42; }") + .matches("get { return $this->a+1; }") + .matches("set { $this->a = $value -1; }") + .matches("final set($value) => $value - 1;") + .matches("set (A $a) { $this->arr[] = $a; $this->a = $a; }") + .matches("get { return implode(', ', array_map(fn (Author $author) => $author->name,$this->authors,)); }") + + .notMatches("name;") + .notMatches("name => 123;") + .notMatches("name;"); + } + +} diff --git a/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/ClassPropertyDeclarationTreeTest.java b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/ClassPropertyDeclarationTreeTest.java index aa806bbb95..90d1701c62 100644 --- a/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/ClassPropertyDeclarationTreeTest.java +++ b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/ClassPropertyDeclarationTreeTest.java @@ -61,12 +61,16 @@ void shouldParseVariable() { .matches("final $a;") .matches("public private $a;") + .matches("public string $a { get; }") + .matches("public string $a { get { return $this-> a + 1; } }") + .matches("public string $a { final set($value) => $value - 1; }") + .notMatches("public final A;") .notMatches("$a;"); } @Test - void shouldConstDeclaration() { + void shouldSupportConstDeclaration() { Assertions.assertThat(PHPLexicalGrammar.CLASS_CONSTANT_DECLARATION) .matches("const A;") .matches("const A, B;") @@ -83,7 +87,7 @@ void shouldConstDeclaration() { } @Test - void variableDeclaration() { + void shouldSupportVariableDeclaration() { ClassPropertyDeclarationTree tree = parse("public final $a, $b, $c;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); assertThat(tree.modifierTokens()).hasSize(2); @@ -98,7 +102,7 @@ void variableDeclaration() { } @Test - void readonlyVariableDeclaration() { + void shouldSupportReadonlyVariableDeclaration() { ClassPropertyDeclarationTree tree = parse("public readonly $prop;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); assertThat(tree.modifierTokens()).hasSize(2); @@ -107,7 +111,7 @@ void readonlyVariableDeclaration() { } @Test - void constantDeclaration() { + void shouldSupportConstantDeclaration() { ClassPropertyDeclarationTree tree = parse("const A, B;", PHPLexicalGrammar.CLASS_CONSTANT_DECLARATION); assertThat(tree.is(Kind.CLASS_CONSTANT_PROPERTY_DECLARATION)).isTrue(); assertThat(tree.modifierTokens()).hasSize(1); @@ -118,7 +122,7 @@ void constantDeclaration() { } @Test - void constantDeclarationWithDeclaredType() { + void shouldSupportConstantDeclarationWithDeclaredType() { ClassPropertyDeclarationTree tree = parse("const string A;", PHPLexicalGrammar.CLASS_CONSTANT_DECLARATION); assertThat(tree.is(Kind.CLASS_CONSTANT_PROPERTY_DECLARATION)).isTrue(); assertThat(tree.modifierTokens()).hasSize(1); @@ -130,7 +134,7 @@ void constantDeclarationWithDeclaredType() { } @Test - void privateConstantDeclaration() { + void shouldSupportPrivateConstantDeclaration() { ClassPropertyDeclarationTree tree = parse("private const A;", PHPLexicalGrammar.CLASS_CONSTANT_DECLARATION); assertThat(tree.is(Kind.CLASS_CONSTANT_PROPERTY_DECLARATION)).isTrue(); assertThat(tree.modifierTokens()).hasSize(2); @@ -139,21 +143,21 @@ void privateConstantDeclaration() { } @Test - void finalConstantDeclaration() { + void shouldSupportFinalConstantDeclaration() { ClassPropertyDeclarationTree tree = parse("final const A;", PHPLexicalGrammar.CLASS_CONSTANT_DECLARATION); assertThat(tree.is(Kind.CLASS_CONSTANT_PROPERTY_DECLARATION)).isTrue(); assertThat(modifier(tree)).containsExactly("final", "const"); } @Test - void protectedFinalConstantDeclaration() { + void shouldSupportProtectedFinalConstantDeclaration() { ClassPropertyDeclarationTree tree = parse("protected final const A;", PHPLexicalGrammar.CLASS_CONSTANT_DECLARATION); assertThat(tree.is(Kind.CLASS_CONSTANT_PROPERTY_DECLARATION)).isTrue(); assertThat(modifier(tree)).containsExactly("protected", "final", "const"); } @Test - void typeAnnotation() { + void shouldSupportTypeAnnotation() { ClassPropertyDeclarationTree tree = parse("public int $id;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); assertThat(tree.typeAnnotation().typeName().is(Kind.BUILT_IN_TYPE)).isTrue(); @@ -168,7 +172,7 @@ void typeAnnotation() { } @Test - void typeAnnotationClassname() { + void shouldSupportTypeAnnotationClassname() { ClassPropertyDeclarationTree tree = parse("public MyClass $id;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); assertThat(tree.typeAnnotation().typeName().is(Kind.NAMESPACE_NAME)).isTrue(); @@ -176,7 +180,7 @@ void typeAnnotationClassname() { } @Test - void typeAnnotationSelfParent() { + void shouldSupportTypeAnnotationSelfParent() { ClassPropertyDeclarationTree tree = parse("public self $id;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); assertThat(builtinType(tree)).isEqualTo("self"); @@ -186,7 +190,7 @@ void typeAnnotationSelfParent() { } @Test - void staticTypeAnnotation() { + void shouldSupportStaticTypeAnnotation() { ClassPropertyDeclarationTree tree = parse("public static iterable $staticProp;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); assertThat(tree.modifierTokens()).extracting(SyntaxToken::text).containsExactly("public", "static"); @@ -194,7 +198,7 @@ void staticTypeAnnotation() { } @Test - void typeAnnotationDefaultValue() { + void shouldSupportTypeAnnotationDefaultValue() { ClassPropertyDeclarationTree tree = parse("private string $str = \"foo\";", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); assertThat(builtinType(tree)).isEqualTo("string"); @@ -202,7 +206,7 @@ void typeAnnotationDefaultValue() { } @Test - void typeAnnotationVar() { + void shouldSupportTypeAnnotationVar() { ClassPropertyDeclarationTree tree = parse("var bool $flag;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); assertThat(builtinType(tree)).isEqualTo("bool"); @@ -210,7 +214,7 @@ void typeAnnotationVar() { } @Test - void typeAnnotationNullable() { + void shouldSupportTypeAnnotationNullable() { ClassPropertyDeclarationTree tree = parse("public ?int $id;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.is(Kind.CLASS_PROPERTY_DECLARATION)).isTrue(); TypeTree type = tree.typeAnnotation(); @@ -219,7 +223,7 @@ void typeAnnotationNullable() { } @Test - void variableWithAttributes() { + void shouldSupportVariableWithAttributes() { ClassPropertyDeclarationTree tree = parse("#[A1(3)] public $x;", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); assertThat(tree.attributeGroups()).hasSize(1); @@ -229,7 +233,7 @@ void variableWithAttributes() { } @Test - void constantWithAttributes() { + void shouldSupportConstantWithAttributes() { ClassPropertyDeclarationTree tree = parse("#[A2(2, 3)] public const FOO = 'foo';", PHPLexicalGrammar.CLASS_CONSTANT_DECLARATION); assertThat(tree.attributeGroups()).hasSize(1); @@ -238,6 +242,20 @@ void constantWithAttributes() { assertThat(tree.attributeGroups().get(0).attributes().get(0).arguments()).hasSize(2); } + @Test + void shouldSupportPropertyHook() { + ClassPropertyDeclarationTree tree = parse("public string $a { get { return $this-> a + 1; } final set($value) => $value - 1; }", PHPLexicalGrammar.CLASS_VARIABLE_DECLARATION); + + assertThat(tree.attributeGroups()).isEmpty(); + assertThat(tree.modifierTokens()).hasSize(1); + assertThat(tree.declarations()).hasSize(1); + assertThat(tree.propertyHooks()).isNotNull(); + assertThat(tree.propertyHooks().openCurlyBrace()).isNotNull(); + assertThat(tree.propertyHooks().hooks()).hasSize(2); + assertThat(tree.propertyHooks().closeCurlyBrace()).isNotNull(); + assertThat(tree.eosToken()).isNull(); + } + /** * Get list of all modifier token texts */ diff --git a/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/PropertyHookListTreeTest.java b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/PropertyHookListTreeTest.java new file mode 100644 index 0000000000..f61859e9ff --- /dev/null +++ b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/PropertyHookListTreeTest.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 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.PropertyHookListTree; +import org.sonar.plugins.php.api.tree.declaration.PropertyHookTree; + +import static org.assertj.core.api.Assertions.assertThat; + +class PropertyHookListTreeTest extends PHPTreeModelTest { + + @Test + void shouldParsePropertyHookList() { + PropertyHookListTree tree = parse("{ #[A1(8), A2] public &get { return $this-> a+1; } }", PHPLexicalGrammar.PROPERTY_HOOK_LIST); + assertThat(tree.is(Tree.Kind.PROPERTY_HOOK_LIST)).isTrue(); + assertThat(tree.openCurlyBrace()).isNotNull(); + assertThat(tree.closeCurlyBrace()).isNotNull(); + assertThat(tree.hooks()).hasSize(1); + + PropertyHookTree firstPropertyHook = tree.hooks().get(0); + assertThat(firstPropertyHook.attributeGroups()).hasSize(1); + assertThat(firstPropertyHook.attributeGroups().get(0).attributes()).hasSize(2); + assertThat(firstPropertyHook.modifiers()).hasSize(1); + assertThat(firstPropertyHook.referenceToken()).isNotNull(); + assertThat(firstPropertyHook.name().text()).isEqualTo("get"); + assertThat(firstPropertyHook.parameters()).isNull(); + assertThat(firstPropertyHook.doubleArrowToken()).isNull(); + assertThat(firstPropertyHook.body().is(Tree.Kind.BLOCK)).isTrue(); + } + + @Test + void shouldParsePropertyHookListWithDoubleArrow() { + PropertyHookListTree tree = parse("{ final set($value) => $value - 1; }", PHPLexicalGrammar.PROPERTY_HOOK_LIST); + assertThat(tree.is(Tree.Kind.PROPERTY_HOOK_LIST)).isTrue(); + assertThat(tree.openCurlyBrace()).isNotNull(); + assertThat(tree.closeCurlyBrace()).isNotNull(); + assertThat(tree.hooks()).hasSize(1); + + PropertyHookTree firstPropertyHook = tree.hooks().get(0); + assertThat(firstPropertyHook.attributeGroups()).isEmpty(); + assertThat(firstPropertyHook.modifiers()).hasSize(1); + assertThat(firstPropertyHook.referenceToken()).isNull(); + assertThat(firstPropertyHook.name().text()).isEqualTo("set"); + assertThat(firstPropertyHook.parameters().parameters()).hasSize(1); + assertThat(firstPropertyHook.parameters().parameters().get(0).variableIdentifier().text()).isEqualTo("$value"); + + assertThat(firstPropertyHook.doubleArrowToken()).isNotNull(); + assertThat(firstPropertyHook.body().is(Tree.Kind.EXPRESSION_STATEMENT)).isTrue(); + } + + @Test + void shouldParsePropertyHookListWithMultiplePropertyHooks() { + PropertyHookListTree tree = parse("{ get; &set; }", PHPLexicalGrammar.PROPERTY_HOOK_LIST); + assertThat(tree.is(Tree.Kind.PROPERTY_HOOK_LIST)).isTrue(); + assertThat(tree.openCurlyBrace()).isNotNull(); + assertThat(tree.closeCurlyBrace()).isNotNull(); + assertThat(tree.hooks()).hasSize(2); + } +} diff --git a/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/PropertyHookTreeTest.java b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/PropertyHookTreeTest.java new file mode 100644 index 0000000000..4493036f79 --- /dev/null +++ b/php-frontend/src/test/java/org/sonar/php/tree/impl/declaration/PropertyHookTreeTest.java @@ -0,0 +1,74 @@ +/* + * 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.PropertyHookTree; + +import static org.assertj.core.api.Assertions.assertThat; + +class PropertyHookTreeTest extends PHPTreeModelTest { + + @Test + void shouldParsePropertyHookMethodDeclaration() { + PropertyHookTree tree = parse("#[A1(8), A2] public &get { return $this-> a+1; }", PHPLexicalGrammar.PROPERTY_HOOK); + assertThat(tree.is(Tree.Kind.PROPERTY_HOOK_METHOD_DECLARATION)).isTrue(); + assertThat(tree.attributeGroups()).hasSize(1); + assertThat(tree.attributeGroups().get(0).attributes()).hasSize(2); + assertThat(tree.modifiers()).hasSize(1); + assertThat(tree.referenceToken()).isNotNull(); + assertThat(tree.name().text()).isEqualTo("get"); + assertThat(tree.parameters()).isNull(); + assertThat(tree.doubleArrowToken()).isNull(); + assertThat(tree.body().is(Tree.Kind.BLOCK)).isTrue(); + } + + @Test + void shouldParsePropertyHookMethodDeclarationWithDoubleArrow() { + PropertyHookTree tree = parse("final set($value) => $value - 1;", PHPLexicalGrammar.PROPERTY_HOOK); + assertThat(tree.is(Tree.Kind.PROPERTY_HOOK_METHOD_DECLARATION)).isTrue(); + assertThat(tree.attributeGroups()).isEmpty(); + assertThat(tree.modifiers()).hasSize(1); + assertThat(tree.referenceToken()).isNull(); + assertThat(tree.name().text()).isEqualTo("set"); + assertThat(tree.parameters().parameters()).hasSize(1); + assertThat(tree.parameters().parameters().get(0).variableIdentifier().text()).isEqualTo("$value"); + + assertThat(tree.doubleArrowToken()).isNotNull(); + assertThat(tree.body().is(Tree.Kind.EXPRESSION_STATEMENT)).isTrue(); + } + + @Test + void shouldParseAbstractPropertyHook() { + PropertyHookTree tree = parse("get;", PHPLexicalGrammar.PROPERTY_HOOK); + assertThat(tree.is(Tree.Kind.PROPERTY_HOOK_METHOD_DECLARATION)).isTrue(); + assertThat(tree.attributeGroups()).isEmpty(); + assertThat(tree.modifiers()).isEmpty(); + assertThat(tree.referenceToken()).isNull(); + assertThat(tree.name().text()).isEqualTo("get"); + assertThat(tree.parameters()).isNull(); + + assertThat(tree.doubleArrowToken()).isNull(); + assertThat(tree.body().is(Tree.Kind.TOKEN)).isTrue(); + } +} 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 47ab457168..ee025445fe 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 @@ -50,7 +50,7 @@ void test() { assertThat(testVisitor.classCounter).isEqualTo(1); assertThat(testVisitor.namespaceNameCounter).isEqualTo(9); - assertThat(testVisitor.varIdentifierCounter).isEqualTo(9); + assertThat(testVisitor.varIdentifierCounter).isEqualTo(12); } @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 4cc90c9e7b..fc781f54ac 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 @@ -38,6 +38,7 @@ 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.PropertyHookTree; import org.sonar.plugins.php.api.tree.declaration.UnionTypeTree; import org.sonar.plugins.php.api.tree.expression.BinaryExpressionTree; import org.sonar.plugins.php.api.tree.expression.CallableConvertTree; @@ -82,16 +83,17 @@ void shouldVisitTreeElements() { assertThat(testVisitor.classCounter).isEqualTo(2); assertThat(testVisitor.namespaceNameCounter).isEqualTo(9); - assertThat(testVisitor.varIdentifierCounter).isEqualTo(9); + assertThat(testVisitor.varIdentifierCounter).isEqualTo(12); // PHPCheck#init() is called by PHPAnalyzer assertThat(testVisitor.initCounter).isZero(); - assertThat(testVisitor.literalCounter).isEqualTo(6); - assertThat(testVisitor.tokenCounter).isEqualTo(105); + assertThat(testVisitor.literalCounter).isEqualTo(7); + assertThat(testVisitor.tokenCounter).isEqualTo(122); 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.propertyHookCounter).isEqualTo(2); assertThat(testVisitor.attributeGroupsCounter).isEqualTo(2); assertThat(testVisitor.attributesCounter).isEqualTo(3); assertThat(testVisitor.enumsCounter).isEqualTo(1); @@ -211,6 +213,7 @@ private class TestVisitor extends PHPVisitorCheck { int intersectionTypeCounter = 0; int dnfTypeCounter = 0; int dnfIntersectionTypeCounter = 0; + int propertyHookCounter = 0; @Override public void visitClassDeclaration(ClassDeclarationTree tree) { @@ -269,6 +272,12 @@ public void visitDnfIntersectionType(DnfIntersectionTypeTree tree) { dnfIntersectionTypeCounter++; } + @Override + public void visitPropertyHook(PropertyHookTree tree) { + super.visitPropertyHook(tree); + propertyHookCounter++; + } + @Override public void visitAttributeGroup(AttributeGroupTree tree) { super.visitAttributeGroup(tree); diff --git a/php-frontend/src/test/java/org/sonar/php/utils/Assertions.java b/php-frontend/src/test/java/org/sonar/php/utils/Assertions.java index f4fbd80eb2..224c235a71 100644 --- a/php-frontend/src/test/java/org/sonar/php/utils/Assertions.java +++ b/php-frontend/src/test/java/org/sonar/php/utils/Assertions.java @@ -97,6 +97,13 @@ public ParserAssert notMatches(String input) { } catch (RecognitionException e) { // expected return this; + } catch (RuntimeException e) { + Throwable rootCause = Throwables.getRootCause(e); + if (rootCause instanceof RecognitionException) { + return this; + } else { + throw e; + } } throw new AssertionError("Rule '" + getRuleName() + "' should not match:\n" + input); } diff --git a/php-frontend/src/test/java/org/sonar/php/utils/collections/IteratorUtilsTest.java b/php-frontend/src/test/java/org/sonar/php/utils/collections/IteratorUtilsTest.java index c6a897d3dd..a882298964 100644 --- a/php-frontend/src/test/java/org/sonar/php/utils/collections/IteratorUtilsTest.java +++ b/php-frontend/src/test/java/org/sonar/php/utils/collections/IteratorUtilsTest.java @@ -72,4 +72,20 @@ void testIteratorConcatWithNull() { assertThat(iterator.hasNext()).isFalse(); assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(iterator::next); } + + @Test + void testNullableIteratorWithExistingElement() { + Iterator iterator = IteratorUtils.nullableIterator("element"); + + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next()).isEqualTo("element"); + } + + @Test + void testNullableIteratorWithNull() { + Iterator iterator = IteratorUtils.nullableIterator(null); + + assertThat(iterator.hasNext()).isFalse(); + assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(iterator::next); + } } diff --git a/php-frontend/src/test/resources/visitors/test.php b/php-frontend/src/test/resources/visitors/test.php index f5ef3ecc69..15b854fc04 100644 --- a/php-frontend/src/test/resources/visitors/test.php +++ b/php-frontend/src/test/resources/visitors/test.php @@ -18,6 +18,11 @@ public function foo(#[A2, A3,] int|array $a, int|(A&B) $b) { public function bar(callable&iterable $b) { return $b; } + + public string $x { + get; + final set($y) => $y - 1; + } } enum A {