Skip to content

Commit 6de9761

Browse files
cirrasfourls
authored andcommitted
Fix FPs around enumerators in UnusedRoutine and UnusedProperty
1 parent 6939f4b commit 6de9761

File tree

14 files changed

+320
-80
lines changed

14 files changed

+320
-80
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- **API:** `EnumeratorOccurrence` type.
13+
- **API:** `EnumeratorOccurrence::getGetEnumerator` method.
14+
- **API:** `EnumeratorOccurrence::getMoveNext` method.
15+
- **API:** `EnumeratorOccurrence::getCurrent` method.
16+
- **API:** `ForInStatementNode.getEnumeratorOccurrence` method.
17+
1018
### Changed
1119

1220
- Detect tab-indented multiline strings in `TabulationCharacter`.
1321
- Improve support for evaluating name references in compiler directive expressions.
1422

23+
### Deprecated
24+
25+
- **API:** `ForInStatementNode::getGetEnumeratorDeclaration` method, get the declaration through
26+
`getEnumeratorOccurrence` instead.
27+
- **API:** `ForInStatementNode::getMoveNextDeclaration` method, get the declaration through
28+
`getEnumeratorOccurrence` instead.
29+
- **API:** `ForInStatementNode::getCurrentDeclaration` method, get the declaration through
30+
`getEnumeratorOccurrence` instead.
31+
1532
### Fixed
1633

34+
- False positives on enumerable method `GetEnumerator` in `UnusedRoutine`.
35+
- False positives on enumerator method `MoveNext` in `UnusedRoutine`.
36+
- False positives on enumerator property `Current` in `UnusedProperty`.
1737
- Name resolution failures in legacy initialization sections referencing the implementation section.
1838
- Incorrect file position calculation for multiline string tokens.
1939

delphi-checks/src/test/java/au/com/integradev/delphi/checks/UnusedPropertyCheckTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,4 +265,32 @@ void testUnusedImplementationPropertyWithExcludeApiShouldAddIssue() {
265265
.appendImpl("end;"))
266266
.verifyIssues();
267267
}
268+
269+
@Test
270+
void testUsedEnumeratorPropertiesShouldNotAddIssue() {
271+
CheckVerifier.newVerifier()
272+
.withCheck(new UnusedPropertyCheck())
273+
.onFile(
274+
new DelphiTestUnitBuilder()
275+
.appendDecl("type")
276+
.appendDecl(" TFooEnumerator = record")
277+
.appendDecl(" function MoveNext: Boolean;")
278+
.appendDecl(" property Current: string read FCurrent;")
279+
.appendDecl(" end;")
280+
.appendDecl(" TFoo = record")
281+
.appendDecl(" private")
282+
.appendDecl(" FCurrent: string;")
283+
.appendDecl(" public")
284+
.appendDecl(" function GetEnumerator: TFooEnumerator;")
285+
.appendDecl(" end;")
286+
.appendImpl("procedure Bar(Foo: TFoo);")
287+
.appendImpl("var")
288+
.appendImpl(" Baz: string;")
289+
.appendImpl("begin")
290+
.appendImpl(" for Baz in Foo do begin")
291+
.appendImpl(" WriteLn(Baz);")
292+
.appendImpl(" end;")
293+
.appendImpl("end;"))
294+
.verifyNoIssues();
295+
}
268296
}

delphi-checks/src/test/java/au/com/integradev/delphi/checks/UnusedRoutineCheckTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,4 +409,31 @@ void testUnusedImplementationRoutineWithExcludeApiShouldAddIssue() {
409409
.appendImpl("end;"))
410410
.verifyIssues();
411411
}
412+
413+
@Test
414+
void testUsedEnumeratorMethodsShouldNotAddIssue() {
415+
CheckVerifier.newVerifier()
416+
.withCheck(new UnusedRoutineCheck())
417+
.onFile(
418+
new DelphiTestUnitBuilder()
419+
.appendDecl("type")
420+
.appendDecl(" TFooEnumerator = record")
421+
.appendDecl(" function MoveNext: Boolean;")
422+
.appendDecl(" property Current: string read FCurrent;")
423+
.appendDecl(" end;")
424+
.appendDecl(" TFoo = record")
425+
.appendDecl(" private")
426+
.appendDecl(" FCurrent: string;")
427+
.appendDecl(" public")
428+
.appendDecl(" function GetEnumerator: TFooEnumerator;")
429+
.appendDecl(" end;")
430+
.appendImpl("var")
431+
.appendImpl(" Foo: TFoo;")
432+
.appendImpl(" Bar: string;")
433+
.appendImpl("begin")
434+
.appendImpl(" for Bar in Foo do begin")
435+
.appendImpl(" WriteLn(Bar);")
436+
.appendImpl(" end;"))
437+
.verifyNoIssues();
438+
}
412439
}

delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/ForInStatementNodeImpl.java

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,47 +18,19 @@
1818
*/
1919
package au.com.integradev.delphi.antlr.ast.node;
2020

21-
import static java.util.Collections.emptyList;
22-
2321
import au.com.integradev.delphi.antlr.ast.visitors.DelphiParserVisitor;
24-
import au.com.integradev.delphi.symbol.resolve.NameResolutionHelper;
25-
import com.google.common.base.Suppliers;
26-
import java.util.function.Supplier;
2722
import javax.annotation.Nullable;
2823
import org.antlr.runtime.Token;
2924
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
3025
import org.sonar.plugins.communitydelphi.api.ast.ForInStatementNode;
3126
import org.sonar.plugins.communitydelphi.api.ast.StatementNode;
27+
import org.sonar.plugins.communitydelphi.api.symbol.EnumeratorOccurrence;
3228
import org.sonar.plugins.communitydelphi.api.symbol.declaration.PropertyNameDeclaration;
3329
import org.sonar.plugins.communitydelphi.api.symbol.declaration.RoutineNameDeclaration;
34-
import org.sonar.plugins.communitydelphi.api.type.Type;
35-
import org.sonar.plugins.communitydelphi.api.type.TypeFactory;
3630

3731
public final class ForInStatementNodeImpl extends ForStatementNodeImpl
3832
implements ForInStatementNode {
39-
private final Supplier<NameResolutionHelper> nameResolutionHelper =
40-
Suppliers.memoize(() -> new NameResolutionHelper(getAst().getDelphiFile().getTypeFactory()));
41-
42-
private final Supplier<RoutineNameDeclaration> getEnumeratorDeclaration =
43-
Suppliers.memoize(
44-
() ->
45-
nameResolutionHelper
46-
.get()
47-
.findMethodMember(this, getEnumerable().getType(), "GetEnumerator", emptyList()));
48-
49-
private final Supplier<RoutineNameDeclaration> moveNextDeclaration =
50-
Suppliers.memoize(
51-
() ->
52-
nameResolutionHelper
53-
.get()
54-
.findMethodMember(this, getEnumeratorType(), "MoveNext", emptyList()));
55-
56-
private final Supplier<PropertyNameDeclaration> currentDeclaration =
57-
Suppliers.memoize(
58-
() ->
59-
nameResolutionHelper
60-
.get()
61-
.findPropertyMember(this, getEnumeratorType(), "Current", emptyList()));
33+
private EnumeratorOccurrence enumeratorOccurrence;
6234

6335
public ForInStatementNodeImpl(Token token) {
6436
super(token);
@@ -69,30 +41,35 @@ public <T> T accept(DelphiParserVisitor<T> visitor, T data) {
6941
return visitor.visit(this, data);
7042
}
7143

44+
@SuppressWarnings("removal")
7245
@Override
7346
@Nullable
7447
public RoutineNameDeclaration getGetEnumeratorDeclaration() {
75-
return getEnumeratorDeclaration.get();
48+
if (getEnumeratorOccurrence() == null) {
49+
return null;
50+
}
51+
return (RoutineNameDeclaration)
52+
getEnumeratorOccurrence().getGetEnumerator().getNameDeclaration();
7653
}
7754

55+
@SuppressWarnings("removal")
7856
@Override
7957
@Nullable
8058
public RoutineNameDeclaration getMoveNextDeclaration() {
81-
return moveNextDeclaration.get();
59+
if (getEnumeratorOccurrence() == null) {
60+
return null;
61+
}
62+
return (RoutineNameDeclaration) getEnumeratorOccurrence().getMoveNext().getNameDeclaration();
8263
}
8364

65+
@SuppressWarnings("removal")
8466
@Override
8567
@Nullable
8668
public PropertyNameDeclaration getCurrentDeclaration() {
87-
return currentDeclaration.get();
88-
}
89-
90-
private Type getEnumeratorType() {
91-
RoutineNameDeclaration enumerator = getGetEnumeratorDeclaration();
92-
if (enumerator != null) {
93-
return enumerator.getReturnType();
69+
if (getEnumeratorOccurrence() == null) {
70+
return null;
9471
}
95-
return TypeFactory.unknownType();
72+
return (PropertyNameDeclaration) getEnumeratorOccurrence().getCurrent().getNameDeclaration();
9673
}
9774

9875
@Override
@@ -104,4 +81,14 @@ public ExpressionNode getEnumerable() {
10481
public StatementNode getStatement() {
10582
return (StatementNode) getChild(4);
10683
}
84+
85+
@Override
86+
@Nullable
87+
public EnumeratorOccurrence getEnumeratorOccurrence() {
88+
return enumeratorOccurrence;
89+
}
90+
91+
public void setEnumeratorOccurrence(EnumeratorOccurrence enumeratorOccurrence) {
92+
this.enumeratorOccurrence = enumeratorOccurrence;
93+
}
10794
}

delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/DependencyAnalysisVisitor.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.sonar.plugins.communitydelphi.api.ast.RoutineImplementationNode;
3636
import org.sonar.plugins.communitydelphi.api.ast.TypeDeclarationNode;
3737
import org.sonar.plugins.communitydelphi.api.ast.utils.ExpressionNodeUtils;
38+
import org.sonar.plugins.communitydelphi.api.symbol.EnumeratorOccurrence;
3839
import org.sonar.plugins.communitydelphi.api.symbol.NameOccurrence;
3940
import org.sonar.plugins.communitydelphi.api.symbol.declaration.NameDeclaration;
4041
import org.sonar.plugins.communitydelphi.api.symbol.declaration.PropertyNameDeclaration;
@@ -184,17 +185,22 @@ public Data visit(ArrayAccessorNode accessorNode, Data data) {
184185

185186
@Override
186187
public Data visit(ForInStatementNode forInStatementNode, Data data) {
187-
RoutineNameDeclaration enumerator = forInStatementNode.getGetEnumeratorDeclaration();
188-
addDependenciesForDeclaration(enumerator, data);
189-
handleInlineRoutines(enumerator, data);
188+
EnumeratorOccurrence enumerator = forInStatementNode.getEnumeratorOccurrence();
190189

191-
RoutineNameDeclaration moveNext = forInStatementNode.getMoveNextDeclaration();
192-
addDependenciesForDeclaration(moveNext, data);
193-
handleInlineRoutines(moveNext, data);
190+
if (enumerator != null) {
191+
var getEnumerator =
192+
(RoutineNameDeclaration) enumerator.getGetEnumerator().getNameDeclaration();
193+
addDependenciesForDeclaration(getEnumerator, data);
194+
handleInlineRoutines(getEnumerator, data);
194195

195-
PropertyNameDeclaration current = forInStatementNode.getCurrentDeclaration();
196-
addDependenciesForDeclaration(current, data);
197-
handleInlineRoutines(current, data);
196+
var moveNext = (RoutineNameDeclaration) enumerator.getMoveNext().getNameDeclaration();
197+
addDependenciesForDeclaration(moveNext, data);
198+
handleInlineRoutines(moveNext, data);
199+
200+
var current = (PropertyNameDeclaration) enumerator.getCurrent().getNameDeclaration();
201+
addDependenciesForDeclaration(current, data);
202+
handleInlineRoutines(current, data);
203+
}
198204

199205
return DelphiParserVisitor.super.visit(forInStatementNode, data);
200206
}

delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolAssociationVisitor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.sonar.plugins.communitydelphi.api.ast.ArrayAccessorNode;
2828
import org.sonar.plugins.communitydelphi.api.ast.DelphiAst;
2929
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
30+
import org.sonar.plugins.communitydelphi.api.ast.ForInStatementNode;
3031
import org.sonar.plugins.communitydelphi.api.ast.NameDeclarationNode;
3132
import org.sonar.plugins.communitydelphi.api.ast.NameReferenceNode;
3233
import org.sonar.plugins.communitydelphi.api.ast.RoutineNameNode;
@@ -98,4 +99,10 @@ public Data visit(RoutineNameNode node, Data data) {
9899
data.fileScope.attach(node);
99100
return DelphiParserVisitor.super.visit(node, data);
100101
}
102+
103+
@Override
104+
public Data visit(ForInStatementNode node, Data data) {
105+
data.fileScope.attach(node);
106+
return DelphiParserVisitor.super.visit(node, data);
107+
}
101108
}

delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,8 @@ public Data visit(ForInStatementNode node, Data data) {
620620
node.getEnumerable().accept(this, data);
621621
node.getVariable().accept(this, data);
622622

623+
data.nameResolutionHelper.resolve(node);
624+
623625
return visitScopeFromRoot(node.getStatement(), data);
624626
}
625627

delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/declaration/VariableNameDeclarationImpl.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.sonar.plugins.communitydelphi.api.ast.VarDeclarationNode;
3737
import org.sonar.plugins.communitydelphi.api.ast.VarStatementNode;
3838
import org.sonar.plugins.communitydelphi.api.ast.Visibility;
39+
import org.sonar.plugins.communitydelphi.api.symbol.EnumeratorOccurrence;
3940
import org.sonar.plugins.communitydelphi.api.symbol.declaration.NameDeclaration;
4041
import org.sonar.plugins.communitydelphi.api.symbol.declaration.VariableNameDeclaration;
4142
import org.sonar.plugins.communitydelphi.api.symbol.scope.DelphiScope;
@@ -148,7 +149,12 @@ private static Type loopVarType(NameDeclarationNode node) {
148149
if (loop instanceof ForToStatementNode) {
149150
typed = ((ForToStatementNode) loop).getInitializerExpression();
150151
} else {
151-
typed = ((ForInStatementNode) loop).getCurrentDeclaration();
152+
EnumeratorOccurrence enumerator = ((ForInStatementNode) loop).getEnumeratorOccurrence();
153+
if (enumerator != null) {
154+
typed = (Typed) enumerator.getCurrent().getNameDeclaration();
155+
} else {
156+
typed = null;
157+
}
152158
}
153159

154160
return getDeclaredTypeWithTypeInferenceFallback(
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Sonar Delphi Plugin
3+
* Copyright (C) 2025 Integrated Application Development
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
18+
*/
19+
package au.com.integradev.delphi.symbol.occurrence;
20+
21+
import org.sonar.plugins.communitydelphi.api.symbol.EnumeratorOccurrence;
22+
import org.sonar.plugins.communitydelphi.api.symbol.NameOccurrence;
23+
24+
public class EnumeratorOccurrenceImpl implements EnumeratorOccurrence {
25+
private final NameOccurrence getEnumerator;
26+
private final NameOccurrence moveNext;
27+
private final NameOccurrence current;
28+
29+
public EnumeratorOccurrenceImpl(
30+
NameOccurrence getEnumerator, NameOccurrence moveNext, NameOccurrence current) {
31+
this.getEnumerator = getEnumerator;
32+
this.moveNext = moveNext;
33+
this.current = current;
34+
}
35+
36+
@Override
37+
public NameOccurrence getGetEnumerator() {
38+
return getEnumerator;
39+
}
40+
41+
@Override
42+
public NameOccurrence getMoveNext() {
43+
return moveNext;
44+
}
45+
46+
@Override
47+
public NameOccurrence getCurrent() {
48+
return current;
49+
}
50+
}

0 commit comments

Comments
 (0)