Skip to content

Commit 5ede9a2

Browse files
SONARPHP-1583 Constructor promoted properties should generate class member symbol (#1336)
1 parent ad606a8 commit 5ede9a2

File tree

7 files changed

+70
-11
lines changed

7 files changed

+70
-11
lines changed

its/ruling/src/integrationTest/resources/expected/PHP_CodeSniffer/php-S1068.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
61,
77
64,
88
67,
9-
73
9+
73,
10+
78
1011
],
1112
"PHP_CodeSniffer:src/Standards/PEAR/Tests/NamingConventions/ValidVariableNameUnitTest.inc": [
1213
19,

php-checks/src/main/java/org/sonar/php/checks/UnusedPrivateFieldCheck.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,9 @@
3232
import org.sonar.plugins.php.api.tree.expression.NameIdentifierTree;
3333
import org.sonar.plugins.php.api.visitors.PHPVisitorCheck;
3434

35-
@Rule(key = UnusedPrivateFieldCheck.KEY)
35+
@Rule(key = "S1068")
3636
public class UnusedPrivateFieldCheck extends PHPVisitorCheck {
3737

38-
public static final String KEY = "S1068";
3938
private static final String MESSAGE = "Remove this unused \"%s\" private field.";
4039

4140
private static final Set<String> constantUsedBeforeInit = new HashSet<>();

php-checks/src/test/java/org/sonar/php/checks/UnusedPrivateFieldCheckTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
class UnusedPrivateFieldCheckTest {
2323

2424
@Test
25-
void test() throws Exception {
25+
void shouldRaiseExpectedIssues() {
2626
CheckVerifier.verify(new UnusedPrivateFieldCheck(), "UnusedPrivateFieldCheck.php");
2727
}
2828
}

php-checks/src/test/resources/checks/UnusedPrivateFieldCheck.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@ class C {
99
private static $field4; // OK
1010
private static $field5; // OK
1111

12+
public function __construct(
13+
public $promotedPublic,
14+
private $promotedPrivateUsed,
15+
private $promotedPrivateUnused // Noncompliant
16+
) {}
17+
1218
public function f($field1) {
13-
return $field1 + $this->field2;
19+
return $field1 + $this->field2 + $this->promotedPrivateUsed;
1420
}
1521

1622
public function g() {
1723
return $this->myArray[0] + self::$field4 + static::$field5;
1824
}
25+
1926
}
2027

2128
class D {

php-frontend/src/main/java/org/sonar/php/tree/symbols/SymbolVisitor.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import java.util.HashMap;
2323
import java.util.Locale;
2424
import java.util.Map;
25+
import java.util.Objects;
2526
import java.util.Set;
27+
import java.util.stream.Stream;
2628
import javax.annotation.Nullable;
2729
import org.sonar.api.utils.Preconditions;
2830
import org.sonar.php.api.PHPKeyword;
@@ -261,8 +263,16 @@ public void visitAnonymousClass(AnonymousClassTree tree) {
261263
private void createMemberSymbols(ClassTree tree, @Nullable TypeSymbolImpl classSymbol) {
262264
for (ClassMemberTree member : tree.members()) {
263265
if (member.is(Kind.METHOD_DECLARATION)) {
264-
SymbolImpl symbol = createMemberSymbol(classSymbol, ((MethodDeclarationTree) member).name(), Symbol.Kind.FUNCTION);
265-
symbol.addModifiers(((MethodDeclarationTree) member).modifiers());
266+
var method = (MethodDeclarationTree) member;
267+
var name = method.name();
268+
269+
SymbolImpl symbol = createMemberSymbol(classSymbol, name, Symbol.Kind.FUNCTION);
270+
symbol.addModifiers(method.modifiers());
271+
272+
if ("__construct".equals(name.text())) {
273+
createMemberSymbolsForPromotedProperties(method, classSymbol);
274+
}
275+
266276
} else if (member.is(Kind.CLASS_CONSTANT_PROPERTY_DECLARATION, Kind.CLASS_PROPERTY_DECLARATION)) {
267277
ClassPropertyDeclarationTree classPropertyDeclaration = (ClassPropertyDeclarationTree) member;
268278
for (VariableDeclarationTree field : classPropertyDeclaration.declarations()) {
@@ -277,6 +287,20 @@ private void createMemberSymbols(ClassTree tree, @Nullable TypeSymbolImpl classS
277287
}
278288
}
279289

290+
private void createMemberSymbolsForPromotedProperties(MethodDeclarationTree method, @Nullable TypeSymbolImpl classSymbol) {
291+
method.parameters().parameters().stream()
292+
.filter(ParameterTree::isPropertyPromotion)
293+
.forEach(param -> {
294+
SymbolImpl fieldSymbol = createMemberSymbol(classSymbol, param.variableIdentifier(), Symbol.Kind.FIELD);
295+
var modifiers = Stream.of(param.visibility(), param.readonlyToken()).filter(Objects::nonNull).toList();
296+
fieldSymbol.addModifiers(modifiers);
297+
ExpressionTree initValue = param.initValue();
298+
if (initValue != null) {
299+
initValue.accept(this);
300+
}
301+
});
302+
}
303+
280304
private SymbolImpl createMemberSymbol(@Nullable TypeSymbolImpl classSymbol, IdentifierTree identifier, Symbol.Kind kind) {
281305
SymbolImpl symbol;
282306
if (classSymbol != null) {

php-frontend/src/test/java/org/sonar/php/tree/symbols/SymbolTableImplTest.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.sonar.php.tree.symbols;
1818

1919
import java.util.List;
20+
import java.util.Objects;
2021
import java.util.stream.Stream;
2122
import org.junit.jupiter.api.Test;
2223
import org.junit.jupiter.params.ParameterizedTest;
@@ -28,6 +29,7 @@
2829
import org.sonar.php.tree.impl.PHPTree;
2930
import org.sonar.php.tree.impl.expression.FunctionCallTreeImpl;
3031
import org.sonar.plugins.php.api.symbols.MemberSymbol;
32+
import org.sonar.plugins.php.api.symbols.QualifiedName;
3133
import org.sonar.plugins.php.api.symbols.Symbol;
3234
import org.sonar.plugins.php.api.symbols.Symbol.Kind;
3335
import org.sonar.plugins.php.api.symbols.TypeSymbol;
@@ -100,12 +102,12 @@ void caseSensitivity() {
100102

101103
@Test
102104
void symbolsFiltering() {
103-
assertThat(SYMBOL_MODEL.getSymbols()).hasSize(20);
105+
assertThat(SYMBOL_MODEL.getSymbols()).hasSize(26);
104106

105-
assertThat(SYMBOL_MODEL.getSymbols(Symbol.Kind.FUNCTION)).hasSize(2);
107+
assertThat(SYMBOL_MODEL.getSymbols(Symbol.Kind.FUNCTION)).hasSize(3);
106108
assertThat(SYMBOL_MODEL.getSymbols(Symbol.Kind.CLASS)).hasSize(1);
107-
assertThat(SYMBOL_MODEL.getSymbols(Symbol.Kind.FIELD)).hasSize(3);
108-
assertThat(SYMBOL_MODEL.getSymbols(Symbol.Kind.PARAMETER)).hasSize(1);
109+
assertThat(SYMBOL_MODEL.getSymbols(Symbol.Kind.FIELD)).hasSize(5);
110+
assertThat(SYMBOL_MODEL.getSymbols(Symbol.Kind.PARAMETER)).hasSize(4);
109111
assertThat(SYMBOL_MODEL.getSymbols(Symbol.Kind.VARIABLE)).hasSize(13);
110112

111113
assertThat(SYMBOL_MODEL.getSymbols("$a")).hasSize(3);
@@ -138,6 +140,30 @@ void testClassFields() {
138140
assertThat(constantField.is(Symbol.Kind.FIELD)).isTrue();
139141
}
140142

143+
@Test
144+
void promotedPropertiesShouldGenerateSymbols() {
145+
assertThat(SYMBOL_MODEL.getSymbols())
146+
.map(Symbol::qualifiedName)
147+
.filteredOn(Objects::nonNull)
148+
.map(QualifiedName::toString)
149+
.contains("a::$promoted1", "a::$promoted2");
150+
}
151+
152+
@Test
153+
void promotedPropertyShouldGenerateFieldAndParamSymbol() {
154+
var symbols = SYMBOL_MODEL.getSymbols("$promoted1");
155+
156+
assertThat(symbols).hasSize(2);
157+
158+
var fieldSymbol = symbols.get(0);
159+
assertThat(fieldSymbol.qualifiedName()).hasToString("a::$promoted1");
160+
assertThat(fieldSymbol.kind()).isEqualTo(Kind.FIELD);
161+
162+
var paramSymbol = symbols.get(1);
163+
assertThat(paramSymbol.qualifiedName()).isNull();
164+
assertThat(paramSymbol.kind()).isEqualTo(Kind.PARAMETER);
165+
}
166+
141167
@Test
142168
void testGlobalConstant() {
143169
Symbol constant = SYMBOL_MODEL.getSymbols("CONSTANT").get(0);

php-frontend/src/test/resources/symbols/symbolTable.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class A { // A
2222

2323
public function f($p = 12) { // f, $p
2424
}
25+
26+
public function __construct(public $promoted1 = 1, private $promoted2, $param) {}
2527
}
2628

2729
$x = function () use($a) {

0 commit comments

Comments
 (0)