Repository: checkstyle/checkstyle

Problem statement:
Add Check Support for Java 17 Sealed Classes: New Check SealedShouldHavePermitsList
child of #14969 :

> the sealed class may omit the permits clause and the Java compiler will infer the permitted subclasses from the declarations in the source file. (The subclasses may be auxiliary or nested classes.) 

A sealed class restricts which other classes can extend it. This is typically achieved using the permits clause.
However, if a sealed class does not specify any permitted subclasses using the permits clause, it implicitly allows any class within the same file to extend it. This can lead to unintended inheritance classes that should not have been allowed to extend the sealed class can do so at any time. 

I propose the implementation of a check that violates any sealed class or interface without a `permits` clause. This check will help ensure that we explicitly defining permitted subclasses, therefore avoiding unintended extensions.


--- 
**Config**

```XML
$ cat config.xml 
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
        "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
    <module name="TreeWalker">
        <module name="SealedShouldHavePermitsList"/>
    </module>
</module>
```

---
**Example**

```Java
public class SealedClasses {
     sealed class A {} // violation
     final class B extends A {}
     final class C extends A {}
     final class D { }    // this is not intended to be a subclass of A
    // but it can do so anytime as there is no restrictive permits clause
 }
```
---
**Expected Output**

```
$ java -jar checkstyle-10.19.0-all.jar -c config.xml SealedClasses.java 
Starting audit...
[ERROR] D:\SealedClasses.java:2:5: 
   Sealed classes or interfaces should explicitly declare permitted subclasses.
Audit done.
Checkstyle ends with 1 errors.
```


Hints:
Can you show what valid code should be (violations corrected by user).

Also, what if we have another input file where sealed class A isn't a sub-class. Either it is the same subclasses as you showed now, or they are sibling classes in the same file.

> ShouldPermitsSubClasses
> subclasses are missing from permits clause of a sealed class
> violates any sealed class or interface without a permits clause

The first 2 sentences seem to conflict with the last sentence in the fact of sub-classes versus regular classes. What if there are no sub-classes in the file, will it still be a violation?

Also, we talked in discord about a `SealedClassShouldHaveExplicitPermitsList` check. Would it make more sense to just implement that type of check, where we violate all seals without a permit list?
> Can you show what valid code should be (violations corrected by user).

```
public class SealedClasses {
     sealed class A permits B, C {}
     final class B extends A {}
     final class C extends A {}
     final class D { }  
 }
```
> Also, what if we have another input file where sealed class A isn't a sub-class.

can you clarify this because sealed class A here isn't a sub-class?

> What if there are no sub-classes in the file, will it still be a violation?

this won't compile => `sealed class must have subclasses`

> Also, we talked in discord about a SealedClassShouldHaveExplicitPermitsList check. Would it make more sense to just implement that type of check, where we violate all seals without a permit list?

yes,  that is true the check name now may be confusing. I will edit it to keep everything consistent. we will violate seals without permits


> Also, what if we have another input file where sealed class A isn't a sub-class.
> can you clarify this because sealed class A here isn't a sub-class?

I consider A a sub-class because it is inside `SealedClasses`. Maybe the proper term is really nested.

> this won't compile => sealed class must have subclasses

So a top-level class can never be sealed as I understand it?
It can be top-level. I think you want to see an example like this: 

```Java
public sealed class Test {
    public final class M extends Test { }
    public final class X extends Test { }
    public final class Y { } // this can extend Test
}

```

the above example is valid after fixing the violation it will be 

```Java
public sealed class Test permits Test.M, Test.X {
    public final class M extends Test { }
    public final class X extends Test { }
    public final class Y { }  // can't extend Test
}
```

so you can say that all nested classes inside top-level sealed classes without `permits` can extend these top-level classes. with `permits` we can restrict the extension to the classes in the `permits` list only
`
I am good to approve this issue if we can update the check's message to `Sealed classes or interfaces should explicitly declare permitted subclasses.`
@rnveach please add approved label when you are good
This check can live in with design checks.

Patch:
diff --git a/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionSealedShouldHavePermitsListTest.java b/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionSealedShouldHavePermitsListTest.java
new file mode 100644
--- /dev/null
+++ b/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionSealedShouldHavePermitsListTest.java
@@ -0,0 +1,96 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library 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 2.1 of the License, or (at your option) any later version.
+//
+// This library 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 library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.checkstyle.suppressionxpathfilter;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
+import com.puppycrawl.tools.checkstyle.checks.design.SealedShouldHavePermitsListCheck;
+
+public class XpathRegressionSealedShouldHavePermitsListTest extends AbstractXpathTestSupport {
+
+    private final String checkName = SealedShouldHavePermitsListCheck.class.getSimpleName();
+
+    @Override
+    protected String getCheckName() {
+        return checkName;
+    }
+
+    @Test
+    public void testInner() throws Exception {
+        final File fileToProcess =
+                new File(getNonCompilablePath("InputXpathSealedShouldHavePermitsListInner.java"));
+
+        final DefaultConfiguration moduleConfig =
+                createModuleConfig(SealedShouldHavePermitsListCheck.class);
+
+        final String[] expectedViolation = {
+            "5:4: " + getCheckMessage(SealedShouldHavePermitsListCheck.class,
+                SealedShouldHavePermitsListCheck.MSG_KEY),
+        };
+
+        final List<String> expectedXpathQueries = Arrays.asList(
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+                        + "[@text='InputXpathSealedShouldHavePermitsListInner']]"
+                        + "/OBJBLOCK/CLASS_DEF[./IDENT[@text='A']]",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+                        + "[@text='InputXpathSealedShouldHavePermitsListInner']]"
+                        + "/OBJBLOCK/CLASS_DEF[./IDENT[@text='A']]/MODIFIERS",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+                        + "[@text='InputXpathSealedShouldHavePermitsListInner']]"
+                        + "/OBJBLOCK/CLASS_DEF[./IDENT[@text='A']]/MODIFIERS/LITERAL_SEALED"
+        );
+
+        runVerifications(moduleConfig, fileToProcess, expectedViolation,
+                expectedXpathQueries);
+    }
+
+    @Test
+    public void testTopLevel() throws Exception {
+        final File fileToProcess =
+                new File(getNonCompilablePath(
+                        "InputXpathSealedShouldHavePermitsListTopLevel.java"));
+
+        final DefaultConfiguration moduleConfig =
+                createModuleConfig(SealedShouldHavePermitsListCheck.class);
+
+        final String[] expectedViolation = {
+            "4:1: " + getCheckMessage(SealedShouldHavePermitsListCheck.class,
+                SealedShouldHavePermitsListCheck.MSG_KEY),
+        };
+
+        final List<String> expectedXpathQueries = Arrays.asList(
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+                + "[@text='InputXpathSealedShouldHavePermitsListTopLevel']]",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+                + "[@text='InputXpathSealedShouldHavePermitsListTopLevel']]/MODIFIERS",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+                + "[@text='InputXpathSealedShouldHavePermitsListTopLevel']]"
+                        + "/MODIFIERS/LITERAL_PUBLIC"
+        );
+
+        runVerifications(moduleConfig, fileToProcess, expectedViolation,
+                expectedXpathQueries);
+    }
+}
diff --git a/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/sealedshouldhavepermitslist/InputXpathSealedShouldHavePermitsListInner.java b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/sealedshouldhavepermitslist/InputXpathSealedShouldHavePermitsListInner.java
new file mode 100644
--- /dev/null
+++ b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/sealedshouldhavepermitslist/InputXpathSealedShouldHavePermitsListInner.java
@@ -0,0 +1,9 @@
+//non-compiled with javac: Compilable with Java17
+package org.checkstyle.suppressionxpathfilter.sealedshouldhavepermitslist;
+
+public class InputXpathSealedShouldHavePermitsListInner {
+   sealed class A {}   // warn
+   final class B extends A {}
+   final class C extends A {}
+   final class D { }
+}
diff --git a/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/sealedshouldhavepermitslist/InputXpathSealedShouldHavePermitsListTopLevel.java b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/sealedshouldhavepermitslist/InputXpathSealedShouldHavePermitsListTopLevel.java
new file mode 100644
--- /dev/null
+++ b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/sealedshouldhavepermitslist/InputXpathSealedShouldHavePermitsListTopLevel.java
@@ -0,0 +1,8 @@
+//non-compiled with javac: Compilable with Java17
+package org.checkstyle.suppressionxpathfilter.sealedshouldhavepermitslist;
+
+public sealed class InputXpathSealedShouldHavePermitsListTopLevel { // warn
+   final class B extends InputXpathSealedShouldHavePermitsListTopLevel {}
+   final class C extends InputXpathSealedShouldHavePermitsListTopLevel {}
+   final class D { }
+}
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java b/src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java
--- a/src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java
@@ -621,6 +621,8 @@ private static void fillChecksFromDesignPackage() {
                 BASE_PACKAGE + ".checks.design.MutableExceptionCheck");
         NAME_TO_FULL_MODULE_NAME.put("OneTopLevelClassCheck",
                 BASE_PACKAGE + ".checks.design.OneTopLevelClassCheck");
+        NAME_TO_FULL_MODULE_NAME.put("SealedShouldHavePermitsListCheck",
+                BASE_PACKAGE + ".checks.design.SealedShouldHavePermitsListCheck");
         NAME_TO_FULL_MODULE_NAME.put("ThrowsCountCheck",
                 BASE_PACKAGE + ".checks.design.ThrowsCountCheck");
         NAME_TO_FULL_MODULE_NAME.put("VisibilityModifierCheck",
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheck.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheck.java
@@ -0,0 +1,96 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library 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 2.1 of the License, or (at your option) any later version.
+//
+// This library 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 library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.design;
+
+import com.puppycrawl.tools.checkstyle.StatelessCheck;
+import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+
+/**
+ * <p>
+ * Checks that sealed classes and interfaces have a permits list.
+ * </p>
+ * <p>
+ *     Rationale: When a permits clause is omitted from a sealed class,
+ *     any class within the same compilation unit can extend it. This differs
+ *     from other sealed classes where permitted subclasses are explicitly
+ *     declared, making them readily visible to the reader. Without a permits
+ *     clause, identifying potential subclasses requires searching the entire
+ *     compilation unit, which can be challenging, especially in large files
+ *     with complex class hierarchies.
+ * </p>
+ * <p>
+ * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-13.html#jls-13.4.2">
+ * Java Language Specification</a> for more information about sealed classes.
+ * </p>
+ * <p>
+ * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
+ * </p>
+ * <p>
+ * Violation Message Keys:
+ * </p>
+ * <ul>
+ * <li>
+ * {@code sealed.should.have.permits}
+ * </li>
+ * </ul>
+ *
+ * @since 10.18.0
+ */
+
+@StatelessCheck
+public class SealedShouldHavePermitsListCheck extends AbstractCheck {
+
+    /**
+     * A key is pointing to the warning message text in "messages.properties"
+     * file.
+     */
+    public static final String MSG_KEY = "sealed.should.have.permits";
+
+    @Override
+    public int[] getDefaultTokens() {
+        return getRequiredTokens();
+    }
+
+    @Override
+    public int[] getAcceptableTokens() {
+        return getRequiredTokens();
+    }
+
+    @Override
+    public int[] getRequiredTokens() {
+        return new int[] {
+            TokenTypes.CLASS_DEF,
+            TokenTypes.INTERFACE_DEF,
+        };
+    }
+
+    @Override
+    public void visitToken(DetailAST ast) {
+        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
+        final boolean isSealed = modifiers.findFirstToken(TokenTypes.LITERAL_SEALED) != null;
+        final boolean hasPermitsList = ast.findFirstToken(TokenTypes.PERMITS_CLAUSE) != null;
+
+        if (isSealed && !hasPermitsList) {
+            log(ast, MSG_KEY);
+        }
+    }
+}
diff --git a/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheckExamplesTest.java b/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheckExamplesTest.java
new file mode 100644
--- /dev/null
+++ b/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheckExamplesTest.java
@@ -0,0 +1,45 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library 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 2.1 of the License, or (at your option) any later version.
+//
+// This library 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 library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.design;
+
+import static com.puppycrawl.tools.checkstyle.checks.design.SealedShouldHavePermitsListCheck.MSG_KEY;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.AbstractExamplesModuleTestSupport;
+
+public class SealedShouldHavePermitsListCheckExamplesTest extends
+        AbstractExamplesModuleTestSupport {
+
+    @Override
+    protected String getPackageLocation() {
+        return "com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist";
+    }
+
+    @Test
+    public void testExample1() throws Exception {
+        final String[] expected = {
+            "18:3: " + getCheckMessage(MSG_KEY),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("Example1.java"),
+                expected);
+    }
+}
diff --git a/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/Example1.java b/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/Example1.java
new file mode 100644
--- /dev/null
+++ b/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/Example1.java
@@ -0,0 +1,31 @@
+/*xml
+<module name="Checker">
+  <module name="TreeWalker">
+    <module name="SealedShouldHavePermitsList"/>
+  </module>
+</module>
+*/
+
+
+//non-compiled with javac: Compilable with Java17
+package com.puppycrawl.tools.checkstyle.checks.design.sealedshouldhavepermitslist;
+
+// xdoc section -- start
+public class Example1 {
+
+  // imagine hundreds of lines of code...
+
+  sealed class A {} // violation
+  final class B extends A {}
+  final class C extends A {}
+  final class D { } // this can extend A, so as any other class in the same file
+}
+
+// all subclasses are declared at the enclosing class level, for easy reading
+class CorrectedExample1 {
+  sealed class A permits B, C {} // ok, explicitly declared permitted subclasses
+  final class B extends A {}
+  final class C extends A {}
+  final class D { } // this can not extend A
+}
+// xdoc section -- end


Test patch:
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheckTest.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/design/SealedShouldHavePermitsListCheckTest.java
@@ -0,0 +1,105 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library 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 2.1 of the License, or (at your option) any later version.
+//
+// This library 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 library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.design;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.puppycrawl.tools.checkstyle.checks.design.SealedShouldHavePermitsListCheck.MSG_KEY;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+
+public class SealedShouldHavePermitsListCheckTest extends AbstractModuleTestSupport {
+
+    @Override
+    protected String getPackageLocation() {
+        return "com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist";
+    }
+
+    @Test
+    public void testGetRequiredTokens() {
+        final SealedShouldHavePermitsListCheck checkObj = new SealedShouldHavePermitsListCheck();
+        final int[] expected = {TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
+        assertWithMessage("Default required tokens are invalid")
+            .that(checkObj.getRequiredTokens())
+            .isEqualTo(expected);
+        assertWithMessage("Default acceptable tokens are invalid")
+            .that(checkObj.getAcceptableTokens())
+            .isEqualTo(expected);
+        assertWithMessage("Default tokens are invalid")
+            .that(checkObj.getDefaultTokens())
+            .isEqualTo(expected);
+    }
+
+    @Test
+    public void testInnerSealedClass() throws Exception {
+        final String[] expected = {
+            "10:5: " + getCheckMessage(MSG_KEY),
+            "15:5: " + getCheckMessage(MSG_KEY),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputSealedShouldHavePermitsListInnerClass.java"),
+                expected);
+    }
+
+    @Test
+    public void testInnerSealedInterface() throws Exception {
+        final String[] expected = {
+            "10:5: " + getCheckMessage(MSG_KEY),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputSealedShouldHavePermitsListInnerInterface.java"),
+                expected);
+    }
+
+    @Test
+    public void testTopLevelSealedClass() throws Exception {
+        final String[] expected = {
+            "9:1: " + getCheckMessage(MSG_KEY),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputSealedShouldHavePermitsListTopLevelSealedClass.java"),
+                expected);
+    }
+
+    @Test
+    public void testTopLevelSealedInterface() throws Exception {
+        final String[] expected = {
+            "9:1: " + getCheckMessage(MSG_KEY),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath(
+                        "InputSealedShouldHavePermitsListTopLevelSealedInterface.java"),
+                expected);
+    }
+
+    @Test
+    public void testJepExample() throws Exception {
+        final String[] expected = {
+            "10:1: " + getCheckMessage(MSG_KEY),
+            "24:1: " + getCheckMessage(MSG_KEY),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath(
+                        "InputSealedShouldHavePermitsListJepExample.java"),
+                expected);
+    }
+}
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
--- a/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
@@ -40,20 +40,20 @@ protected String getPackageLocation() {
 
     @Test
     public void test() {
-        assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny()).hasSize(204);
+        assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny()).hasSize(205);
     }
 
     @Test
     public void testDuplicatePackage() {
         assertThat(XmlMetaReader
                     .readAllModulesIncludingThirdPartyIfAny("com.puppycrawl.tools.checkstyle.meta"))
-                .hasSize(204);
+                .hasSize(205);
     }
 
     @Test
     public void testBadPackage() {
         assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny("DOES.NOT.EXIST"))
-                .hasSize(204);
+                .hasSize(205);
     }
 
     @Test
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListInnerClass.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListInnerClass.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListInnerClass.java
@@ -0,0 +1,31 @@
+/*
+SealedShouldHavePermitsList
+
+*/
+
+//non-compiled with javac: Compilable with Java17
+package com.puppycrawl.tools.checkstyle.checks.design.sealedshouldhavepermitslist;
+
+public class InputSealedShouldHavePermitsListInnerClass {
+    sealed class A {} // violation
+    final class B extends A {}
+    final class C extends A {}
+    final class D { } // this can extend A, so as any other class in the compilation unit
+    non-sealed class F extends A {}
+    sealed class G extends A {} // violation
+    final class I extends G {}
+    enum E {}
+    record R(int x) {}
+}
+
+class InputSealedShouldHavePermitsListInnerClassCorrected {
+    sealed class A permits B, C, F, G { } // ok, explicitly declared the permitted subclasses
+    final class B extends A { }
+    final class C extends A { }
+    final class D { }    // this can't extend A
+    non-sealed class F extends A {}
+    sealed class G extends A permits I {}
+    final class I extends G {}
+    enum E {}
+    record R(int x) {}
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListInnerInterface.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListInnerInterface.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListInnerInterface.java
@@ -0,0 +1,29 @@
+/*
+SealedShouldHavePermitsList
+
+*/
+
+//non-compiled with javac: Compilable with Java17
+package com.puppycrawl.tools.checkstyle.checks.design.sealedshouldhavepermitslist;
+
+public class InputSealedShouldHavePermitsListInnerInterface {
+    sealed interface A {} // violation
+    final class B implements A {}
+    final class C implements A {}
+    final class D { } // this can implement A, so as any other class in the compilation unit
+    non-sealed class F implements A {}
+    static final class G {}
+    record R(int x) implements A {}
+    enum E implements A {}
+}
+
+class InputSealedShouldHavePermitsListInnerInterfaceCorrected {
+    sealed interface A permits B, C, F, R, E {} // ok, explicitly declared the permitted subclasses
+    final class B implements A { }
+    final class C implements A { }
+    final class D { }    // this can't implement A
+    non-sealed class F implements A {}
+    static final class G {}
+    record R(int x) implements A {}
+    enum E implements A {}
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListJepExample.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListJepExample.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListJepExample.java
@@ -0,0 +1,29 @@
+/*
+SealedShouldHavePermitsList
+
+*/
+
+//non-compiled with javac: Compilable with Java17
+package com.puppycrawl.tools.checkstyle.checks.design.sealedshouldhavepermitslist;
+
+// violation below
+public sealed class InputSealedShouldHavePermitsListJepExample
+        // The permits clause has been omitted
+        // as its permitted classes have been
+        // defined in the same file.
+{ }
+
+final class Circle extends InputSealedShouldHavePermitsListJepExample {
+    float radius;
+}
+non-sealed class Square extends InputSealedShouldHavePermitsListJepExample {
+    float side;
+}
+
+// violation below
+sealed class Rectangle extends InputSealedShouldHavePermitsListJepExample {
+    float length, width;
+}
+final class FilledRectangle extends Rectangle {
+    int red, green, blue;
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListTopLevelSealedClass.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListTopLevelSealedClass.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListTopLevelSealedClass.java
@@ -0,0 +1,28 @@
+/*
+SealedShouldHavePermitsList
+
+*/
+
+//non-compiled with javac: Compilable with Java17
+package com.puppycrawl.tools.checkstyle.checks.design.sealedshouldhavepermitslist;
+
+public sealed class InputSealedShouldHavePermitsListTopLevelSealedClass { // violation
+    final class B extends InputSealedShouldHavePermitsListTopLevelSealedClass {}
+    final class C extends InputSealedShouldHavePermitsListTopLevelSealedClass {}
+    final class D { }
+    record R(int x) {}
+    enum E {}
+    static final class F extends InputSealedShouldHavePermitsListTopLevelSealedClass {}
+}
+
+sealed class InputSealedShouldHavePermitsListTopLevelSealedClassCorrected
+        permits InputSealedShouldHavePermitsListTopLevelSealedClassCorrected.B,
+                InputSealedShouldHavePermitsListTopLevelSealedClassCorrected.C,
+                InputSealedShouldHavePermitsListTopLevelSealedClassCorrected.F {
+    final class B extends InputSealedShouldHavePermitsListTopLevelSealedClassCorrected { }
+    final class C extends InputSealedShouldHavePermitsListTopLevelSealedClassCorrected { }
+    final class D { }
+    record R(int x) {}
+    enum E {}
+    static final class F extends InputSealedShouldHavePermitsListTopLevelSealedClassCorrected {}
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListTopLevelSealedInterface.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListTopLevelSealedInterface.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/design/sealedshouldhavepermitslist/InputSealedShouldHavePermitsListTopLevelSealedInterface.java
@@ -0,0 +1,32 @@
+/*
+SealedShouldHavePermitsList
+
+*/
+
+//non-compiled with javac: Compilable with Java17
+package com.puppycrawl.tools.checkstyle.checks.design.sealedshouldhavepermitslist;
+
+public sealed interface InputSealedShouldHavePermitsListTopLevelSealedInterface { // violation
+    final class B implements InputSealedShouldHavePermitsListTopLevelSealedInterface {}
+    final class C implements InputSealedShouldHavePermitsListTopLevelSealedInterface {}
+    final class D { }
+    record R(int x) implements InputSealedShouldHavePermitsListTopLevelSealedInterface {}
+    enum E implements InputSealedShouldHavePermitsListTopLevelSealedInterface {}
+    static non-sealed class S implements InputSealedShouldHavePermitsListTopLevelSealedInterface {}
+}
+
+sealed interface InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected
+        permits InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected.B,
+                InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected.C,
+                InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected.R,
+                InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected.E,
+                InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected.S {
+
+    final class B implements InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected { }
+    final class C implements InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected { }
+    final class D { }
+    record R(int x) implements InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected {}
+    enum E implements InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected {}
+    static non-sealed class S implements
+            InputSealedShouldHavePermitsListTopLevelSealedInterfaceCorrected {}
+}