Skip to content

Commit

Permalink
Implement FormFmx check
Browse files Browse the repository at this point in the history
  • Loading branch information
Cirras authored and fourls committed Jan 28, 2024
1 parent 92f9927 commit 7092267
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for the `LLVM` symbol, which is defined on LLVM-based toolchains from Delphi 12 onward.
- Support for the `IOSSIMULATOR` symbol, which is defined on the `DCCIOSSIMARM64` toolchain.
- `FormDfm` analysis rule, which flags VCL forms/frames that lack a `.dfm` resource.
- `FormFmx` analysis rule, which flags FireMonkey forms/frames that lack a `.fmx` resource.
- **API:** `CompilerDirectiveParser` now returns a new `ResourceDirective` type when parsing
[resource directives](https://docwiki.embarcadero.com/RADStudio/en/Resource_file_(Delphi)).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public final class CheckList {
ForbiddenRoutineCheck.class,
ForbiddenTypeCheck.class,
FormDfmCheck.class,
FormFmxCheck.class,
FreeAndNilTObjectCheck.class,
GotoStatementCheck.class,
GroupedFieldDeclarationCheck.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Sonar Delphi Plugin
* Copyright (C) 2024 Integrated Application Development
*
* 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 02
*/
package au.com.integradev.delphi.checks;

import org.sonar.check.Rule;

@Rule(key = "FormFmx")
public class FormFmxCheck extends AbstractFormResourceCheck {
@Override
protected String getFrameworkName() {
return "FireMonkey";
}

@Override
protected String getFormTypeImage() {
return "FMX.Forms.TForm";
}

@Override
protected String getFrameTypeImage() {
return "FMX.Forms.TFrame";
}

@Override
protected String getResourceFileExtension() {
return "fmx";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<h2>Why is this an issue?</h2>
<p>
Omitting the corresponding <code>.fmx</code> resource for a FireMonkey form or frame is typically
a programming error. While there are some cases that won't cause problems at runtime, it remains
unintuitive and confusing.
</p>
<p>
Additionally, a unit that doesn't contain a <code>.fmx</code> resource will lack design-time form
editing functionality.
</p>
<p>
This rule flags any <code>TForm</code> or <code>TFrame</code> descendant in a unit that doesn't
include a <code>.fmx</code> resource.
</p>
<h2>How to fix it</h2>
<p>Add a <code>.fmx</code> resource to the unit:</p>
<pre data-diff-id="1" data-diff-type="noncompliant">
unit Foo;

interface

uses
FMX.Forms;

type
TFoo = class(TForm)
// ...
end;

implementation

end.
</pre>
<pre data-diff-id="1" data-diff-type="compliant">
unit Foo;

interface

uses
FMX.Forms;

type
TFoo = class(TForm)
// ...
end;

implementation

{$R *.fmx}

end.
</pre>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"title": "FireMonkey forms and frames should have a corresponding .fmx form file",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant/Issue",
"constantCost": "5min"
},
"code": {
"attribute": "COMPLETE",
"impacts": {
"MAINTAINABILITY": "MEDIUM"
}
},
"tags": ["bad-practice", "confusing"],
"defaultSeverity": "Major",
"scope": "ALL",
"quickfix": "unknown"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Sonar Delphi Plugin
* Copyright (C) 2024 Integrated Application Development
*
* 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 02
*/
package au.com.integradev.delphi.checks;

import au.com.integradev.delphi.builders.DelphiTestUnitBuilder;
import au.com.integradev.delphi.checks.verifier.CheckVerifier;
import org.junit.jupiter.api.Test;

class FormFmxCheckTest {
@Test
void testNormalClassWithoutFmxShouldNotAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("type")
.appendDecl(" TFoo = class(TObject)")
.appendDecl(" end;"))
.verifyNoIssues();
}

@Test
void testAliasesWithoutFmxShouldNotAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.withSearchPathUnit(createFmxForms())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("uses")
.appendDecl(" FMX.Forms;")
.appendDecl("type")
.appendDecl(" TFormWeakAlias = TForm;")
.appendDecl(" TFrameWeakAlias = TFrame;")
.appendDecl(" TFormStrongAlias = type TForm;")
.appendDecl(" TFrameStrongAlias = type TFrame;"))
.verifyNoIssues();
}

@Test
void testFormWithFmxShouldNotAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.withSearchPathUnit(createFmxForms())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("uses")
.appendDecl(" FMX.Forms;")
.appendDecl("type")
.appendDecl(" TFoo = class(TForm)")
.appendDecl(" end;")
.appendImpl("{$R Foo.fmx}"))
.verifyNoIssues();
}

@Test
void testFrameWithFmxShouldNotAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.withSearchPathUnit(createFmxForms())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("uses")
.appendDecl(" FMX.Forms;")
.appendDecl("type")
.appendDecl(" TFoo = class(TFrame)")
.appendDecl(" end;")
.appendImpl("{$R Foo.fmx}"))
.verifyNoIssues();
}

@Test
void testFormWithoutFmxShouldAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.withSearchPathUnit(createFmxForms())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("uses")
.appendDecl(" FMX.Forms;")
.appendDecl("type")
.appendDecl(" TFoo = class(TForm) // Noncompliant")
.appendDecl(" end;"))
.verifyIssues();
}

@Test
void testFrameWithoutFmxShouldAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.withSearchPathUnit(createFmxForms())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("uses")
.appendDecl(" FMX.Forms;")
.appendDecl("type")
.appendDecl(" TFoo = class(TFrame) // Noncompliant")
.appendDecl(" end;"))
.verifyIssues();
}

@Test
void testNonFmxResourceShouldAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.withSearchPathUnit(createFmxForms())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("uses")
.appendDecl(" FMX.Forms;")
.appendDecl("type")
.appendDecl(" TFoo = class(TForm) // Noncompliant")
.appendDecl(" end;")
.appendImpl("{$R Foo.bar}"))
.verifyIssues();
}

@Test
void testNonFmxResourceContainingDotFmxShouldAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.withSearchPathUnit(createFmxForms())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("uses")
.appendDecl(" FMX.Forms;")
.appendDecl("type")
.appendDecl(" TFoo = class(TForm) // Noncompliant")
.appendDecl(" end;")
.appendImpl("{$R Foo.fmx.bar}"))
.verifyIssues();
}

@Test
void testIncludeFmxShouldAddIssue() {
CheckVerifier.newVerifier()
.withCheck(new FormFmxCheck())
.withSearchPathUnit(createFmxForms())
.onFile(
new DelphiTestUnitBuilder()
.appendDecl("uses")
.appendDecl(" FMX.Forms;")
.appendDecl("type")
.appendDecl(" TFoo = class(TForm) // Noncompliant")
.appendDecl(" end;")
.appendImpl("{$I Foo.fmx}"))
.verifyIssues();
}

private static DelphiTestUnitBuilder createFmxForms() {
return new DelphiTestUnitBuilder()
.unitName("FMX.Forms")
.appendDecl("type")
.appendDecl(" TForm = class")
.appendDecl(" end;")
.appendDecl(" TFrame = class")
.appendDecl(" end;");
}
}

0 comments on commit 7092267

Please sign in to comment.