diff --git a/CHANGES.md b/CHANGES.md index 01bdc7c7..3b7efcb1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,7 @@ - Add UndefinedVariable check to Sonar way profile. - Add ability to override aliases path and environment path in tasks in VSCode extension. - Set default value for `whitelist` in VariableNamingCheck. +- Add FileMustStartWithPackageStatement check to test if a file starts with a `_package`-statement. - Several fixes. ### Breaking changes (reiterated from above) @@ -38,6 +39,7 @@ - Add SimplifyIf check to Sonar way profile. This might result in more issues. - Add UnsafeEvaluateInvocation check to Sonar way profile. This might result in more issues. - Add UndefinedVariable check to Sonar way profile. This might result in more issues. +- Add FileMustStartWithPackageStatement check to test if a file starts with a `_package`-statement. This might result in more issues. ## 0.10.1 (2024-08-14) diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/CheckList.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/CheckList.java index 34bbd829..6de3cc4e 100644 --- a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/CheckList.java +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/CheckList.java @@ -7,6 +7,7 @@ import nl.ramsolutions.sw.magik.checks.checks.EmptyBlockCheck; import nl.ramsolutions.sw.magik.checks.checks.ExemplarSlotCountCheck; import nl.ramsolutions.sw.magik.checks.checks.FileMethodCountCheck; +import nl.ramsolutions.sw.magik.checks.checks.FileMustStartWithPackageStatementCheck; import nl.ramsolutions.sw.magik.checks.checks.FileNotInLoadListCheck; import nl.ramsolutions.sw.magik.checks.checks.ForbiddenCallCheck; import nl.ramsolutions.sw.magik.checks.checks.ForbiddenGlobalUsageCheck; @@ -68,6 +69,7 @@ public static List> getChecks() { ExemplarSlotCountCheck.class, FileMethodCountCheck.class, FileNotInLoadListCheck.class, + FileMustStartWithPackageStatementCheck.class, ForbiddenCallCheck.class, ForbiddenGlobalUsageCheck.class, ForbiddenInheritanceCheck.class, diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/FileMustStartWithPackageStatementCheck.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/FileMustStartWithPackageStatementCheck.java new file mode 100644 index 00000000..ce165207 --- /dev/null +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/FileMustStartWithPackageStatementCheck.java @@ -0,0 +1,33 @@ +package nl.ramsolutions.sw.magik.checks.checks; + +import com.sonar.sslr.api.AstNode; +import java.util.List; +import nl.ramsolutions.sw.magik.api.MagikGrammar; +import nl.ramsolutions.sw.magik.checks.MagikCheck; +import org.sonar.check.Rule; + +/** Check if file starts with a _package-statement. */ +@Rule(key = FileMustStartWithPackageStatementCheck.CHECK_KEY) +public class FileMustStartWithPackageStatementCheck extends MagikCheck { + + @SuppressWarnings("checkstyle:JavadocVariable") + public static final String CHECK_KEY = "FileMustStartWithPackageStatement"; + + private static final String MESSAGE = "File must start with _package-statement."; + + @Override + protected void walkPostMagik(final AstNode node) { + if (!this.hasPackageStatement(node)) { + this.addFileIssue(MESSAGE); + } + } + + private boolean hasPackageStatement(final AstNode node) { + final List children = node.getChildren(); + if (children.isEmpty()) { + return false; + } + + return children.get(0).is(MagikGrammar.PACKAGE_SPECIFICATION); + } +} diff --git a/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/FileMustStartWithPackageStatement.html b/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/FileMustStartWithPackageStatement.html new file mode 100644 index 00000000..b8484480 --- /dev/null +++ b/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/FileMustStartWithPackageStatement.html @@ -0,0 +1,17 @@ +

File must start with a _package-statement. This ensures the code is loaded into the correct package.

+

Non-compliant Code Example

+
+
+_block
+  write(a)
+_endblock
+
+

Compliant Code Example

+
+
+_package user
+
+_block
+  write(a)
+_endblock
+
diff --git a/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/FileMustStartWithPackageStatement.json b/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/FileMustStartWithPackageStatement.json new file mode 100644 index 00000000..5f587252 --- /dev/null +++ b/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/FileMustStartWithPackageStatement.json @@ -0,0 +1,16 @@ +{ + "title": "File must start with a package-statement", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "bad-practice", + "confusing" + ], + "defaultSeverity": "Major", + "ruleSpecification": "FileMustStartWithPackageStatement", + "sqKey": "file-must-start-with-package-statement" +} diff --git a/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/Sonar_way_profile.json b/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/Sonar_way_profile.json index 5035c3c6..cd067089 100644 --- a/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/Sonar_way_profile.json +++ b/magik-checks/src/main/resources/nl/ramsolutions/sw/sonar/l10n/magik/rules/Sonar_way_profile.json @@ -6,6 +6,7 @@ "EmptyBlock", "ExemplarSlotCount", "FileNotInLoadList", + "FileWithoutPackageStatement", "ForbiddenCall", "ForbiddenGlobalUsage", "HidesVariable", diff --git a/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/FileMustStartWithPackageStatementCheckTest.java b/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/FileMustStartWithPackageStatementCheckTest.java new file mode 100644 index 00000000..9a69102a --- /dev/null +++ b/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/FileMustStartWithPackageStatementCheckTest.java @@ -0,0 +1,54 @@ +package nl.ramsolutions.sw.magik.checks.checks; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import nl.ramsolutions.sw.magik.checks.MagikCheck; +import nl.ramsolutions.sw.magik.checks.MagikIssue; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** Test FileWithoutPackageStatementCheck. */ +class FileMustStartWithPackageStatementCheckTest extends MagikCheckTestBase { + + @ParameterizedTest + @ValueSource( + strings = { + """ + _package user + + _method a.m1 _endmethod + """, + """ + # This is just a comment + _package user + + _method a.m1 _endmethod + """, + }) + void testValid(final String code) { + final MagikCheck check = new FileMustStartWithPackageStatementCheck(); + final List issues = this.runCheck(code, check); + assertThat(issues).isEmpty(); + } + + @ParameterizedTest + @ValueSource( + strings = { + """ + _method a.m1 _endmethod + """, + """ + _method a.m1 _endmethod + + _package user + + _method a.m2 _endmethod + """, + }) + void testInvalid(final String code) { + final MagikCheck check = new FileMustStartWithPackageStatementCheck(); + final List issues = this.runCheck(code, check); + assertThat(issues).hasSize(1); + } +}