Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Parsing errors on attributes preceding visibility sections.
- Parsing errors on attributes preceding `var` sections in type bodies.
- Errors when parsing coverage reports if duplicate file names were present in the project.
- False positives on custom managed records with an `Initialize` operator in
`VariableInitialization`.

## [1.21.0] - 2026-09-04

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.sonar.plugins.communitydelphi.api.operator.UnaryOperator;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.NameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.PropertyNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.RoutineKind;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.RoutineNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypeNameDeclaration;
import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypedDeclaration;
Expand Down Expand Up @@ -689,7 +690,22 @@ private static boolean isUnmanagedVariable(NameDeclaration declaration) {
}

private static boolean isUnmanagedType(Type type) {
return !type.isString() && !type.isArray() && !type.isVariant() && !type.isInterface();
return !type.isString()
&& !type.isArray()
&& !type.isVariant()
&& !type.isInterface()
&& !hasInitializeOperator(type);
}

private static boolean hasInitializeOperator(Type type) {
// Custom managed records are initialized automatically by their Initialize operator.
return type.isRecord()
&& ((StructType) type)
.typeScope().getRoutineDeclarations().stream()
.anyMatch(
routine ->
routine.getRoutineKind() == RoutineKind.OPERATOR
&& routine.getName().equalsIgnoreCase("Initialize"));
}

private interface InitializationState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ <h2>Why is this an issue?</h2>
<li>Variants</li>
<li>Interfaces</li>
<li>Arrays of the above types</li>
<li>Custom managed records with an <code>Initialize</code> operator</li>
</ul>
<h2>How to fix it</h2>
<p>Assign the variable with an initial value before using it.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,174 @@ void testRecordWithManagedFieldsShouldNotAddIssue() {
.verifyNoIssues();
}

// See: https://github.com/integrated-application-development/sonar-delphi/issues/468
@Test
void testCustomManagedRecordWithInitializeOperatorShouldNotAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new VariableInitializationCheck())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("type")
.appendDecl(" TBar = record")
.appendDecl(" Code: Integer;")
.appendDecl(" Name: string;")
.appendDecl(" class operator Initialize(out Dest: TBar);")
.appendDecl(" class operator Finalize(var Dest: TBar);")
.appendDecl(" end;")
.appendDecl("procedure Foo(const Bar: TBar);")
.appendImpl("class operator TBar.Initialize(out Dest: TBar);")
.appendImpl("begin")
.appendImpl(" Dest.Code := 0;")
.appendImpl(" Dest.Name := '';")
.appendImpl("end;")
.appendImpl("class operator TBar.Finalize(var Dest: TBar);")
.appendImpl("begin")
.appendImpl(" Dest.Name := '';")
.appendImpl("end;")
.appendImpl("procedure Test;")
.appendImpl("var")
.appendImpl(" Bar: TBar;")
.appendImpl("begin")
.appendImpl(" Bar.Name := 'Example';")
.appendImpl(" Foo(Bar);")
.appendImpl("end;"))
.verifyNoIssues();
}

@Test
void testCustomManagedRecordWithParameterlessInitializeOperatorShouldNotAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new VariableInitializationCheck())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("type")
.appendDecl(" TBar = record")
.appendDecl(" Int: Integer;")
.appendDecl(" class operator Initialize;")
.appendDecl(" end;")
.appendDecl("procedure Foo(Bar: TBar);")
.appendImpl("procedure Test;")
.appendImpl("var")
.appendImpl(" Bar: TBar;")
.appendImpl("begin")
.appendImpl(" Foo(Bar);")
.appendImpl("end;"))
.verifyNoIssues();
}

@Test
void testInlineCustomManagedRecordWithInitializeOperatorShouldNotAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new VariableInitializationCheck())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("type")
.appendDecl(" TBar = record")
.appendDecl(" Int: Integer;")
.appendDecl(" class operator Initialize(out Dest: TBar);")
.appendDecl(" end;")
.appendDecl("procedure Foo(Bar: TBar);")
.appendImpl("procedure Test;")
.appendImpl("begin")
.appendImpl(" var Bar: TBar;")
.appendImpl(" Foo(Bar);")
.appendImpl("end;"))
.verifyNoIssues();
}

@Test
void testRecordWithCustomManagedRecordFieldShouldNotAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new VariableInitializationCheck())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("type")
.appendDecl(" TBaz = record")
.appendDecl(" Int: Integer;")
.appendDecl(" class operator Initialize(out Dest: TBaz);")
.appendDecl(" end;")
.appendDecl(" TBar = record")
.appendDecl(" Int: Integer;")
.appendDecl(" Baz: TBaz;")
.appendDecl(" end;")
.appendDecl("procedure Foo(Bar: TBar);")
.appendDecl("procedure Flarp(Int: Integer);")
.appendImpl("procedure Test;")
.appendImpl("var")
.appendImpl(" Bar: TBar;")
.appendImpl("begin")
.appendImpl(" Flarp(Bar.Baz.Int);")
.appendImpl(" Bar.Int := 123;")
.appendImpl(" Foo(Bar);")
.appendImpl("end;"))
.verifyNoIssues();
}

@Test
void testRecordWithCustomManagedRecordFieldAndUninitializedFieldShouldAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new VariableInitializationCheck())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("type")
.appendDecl(" TBaz = record")
.appendDecl(" Int: Integer;")
.appendDecl(" class operator Initialize(out Dest: TBaz);")
.appendDecl(" end;")
.appendDecl(" TBar = record")
.appendDecl(" Int: Integer;")
.appendDecl(" Baz: TBaz;")
.appendDecl(" end;")
.appendDecl("procedure Foo(Bar: TBar);")
.appendImpl("procedure Test;")
.appendImpl("var")
.appendImpl(" Bar: TBar;")
.appendImpl("begin")
.appendImpl(" Foo(Bar); // Noncompliant")
.appendImpl("end;"))
.verifyIssues();
}

@Test
void testCustomManagedRecordWithOnlyFinalizeOperatorShouldAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new VariableInitializationCheck())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("type")
.appendDecl(" TBar = record")
.appendDecl(" Int: Integer;")
.appendDecl(" class operator Finalize(var Dest: TBar);")
.appendDecl(" end;")
.appendDecl("procedure Foo(Bar: TBar);")
.appendImpl("procedure Test;")
.appendImpl("var")
.appendImpl(" Bar: TBar;")
.appendImpl("begin")
.appendImpl(" Foo(Bar); // Noncompliant")
.appendImpl("end;"))
.verifyIssues();
}

@Test
void testCustomManagedRecordOutParameterShouldAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new VariableInitializationCheck())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("type")
.appendDecl(" TBar = record")
.appendDecl(" Int: Integer;")
.appendDecl(" class operator Initialize(out Dest: TBar);")
.appendDecl(" end;")
.appendDecl("procedure Foo(Bar: TBar);")
.appendImpl("procedure Test(out Bar: TBar);")
.appendImpl("begin")
.appendImpl(" Foo(Bar); // Noncompliant")
.appendImpl("end;"))
.verifyIssues();
}

@Test
void testUninitializedRecordFieldAssignedToVariableShouldAddIssue() {
CheckVerifier.newVerifier()
Expand Down
Loading