public ClassNode visitClassDeclaration()

in src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java [1156:1362]


    public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) {
        String packageName = Optional.ofNullable(this.moduleNode.getPackageName()).orElse("");
        String className = this.visitIdentifier(ctx.identifier());
        if ("var".equals(className)) {
            throw createParsingFailedException("var cannot be used for type declarations", ctx.identifier());
        }

        boolean isAnnotation = asBoolean(ctx.AT());
        if (isAnnotation) {
            if (asBoolean(ctx.typeParameters())) {
                throw createParsingFailedException("annotation declaration cannot have type parameters", ctx.typeParameters());
            }

            if (asBoolean(ctx.EXTENDS())) {
                throw createParsingFailedException("No extends clause allowed for annotation declaration", ctx.EXTENDS());
            }

            if (asBoolean(ctx.IMPLEMENTS())) {
                throw createParsingFailedException("No implements clause allowed for annotation declaration", ctx.IMPLEMENTS());
            }
        }

        boolean isEnum = asBoolean(ctx.ENUM());
        if (isEnum) {
            if (asBoolean(ctx.typeParameters())) {
                throw createParsingFailedException("enum declaration cannot have type parameters", ctx.typeParameters());
            }

            if (asBoolean(ctx.EXTENDS())) {
                throw createParsingFailedException("No extends clause allowed for enum declaration", ctx.EXTENDS());
            }
        }

        boolean isInterface = (asBoolean(ctx.INTERFACE()) && !isAnnotation);
        if (isInterface) {
            if (asBoolean(ctx.IMPLEMENTS())) {
                throw createParsingFailedException("No implements clause allowed for interface declaration", ctx.IMPLEMENTS());
            }
        }

        ModifierManager modifierManager = new ModifierManager(this, ctx.getNodeMetaData(TYPE_DECLARATION_MODIFIERS));

        Optional<ModifierNode> finalModifier = modifierManager.get(FINAL);
        Optional<ModifierNode> sealedModifier = modifierManager.get(SEALED);
        Optional<ModifierNode> nonSealedModifier = modifierManager.get(NON_SEALED);
        boolean isFinal = finalModifier.isPresent();
        boolean isSealed = sealedModifier.isPresent();
        boolean isNonSealed = nonSealedModifier.isPresent();

        boolean isRecord = asBoolean(ctx.RECORD());
        boolean hasRecordHeader = asBoolean(ctx.formalParameters());
        if (isRecord) {
            if (!hasRecordHeader) {
                throw createParsingFailedException("header declaration of record is expected", ctx.identifier());
            }
            if (asBoolean(ctx.EXTENDS())) {
                throw createParsingFailedException("No extends clause allowed for record declaration", ctx.EXTENDS());
            }
            if (isSealed) {
                throw createParsingFailedException("`sealed` is not allowed for record declaration", sealedModifier.get());
            }
            if (isNonSealed) {
                throw createParsingFailedException("`non-sealed` is not allowed for record declaration", nonSealedModifier.get());
            }
        } else {
            if (hasRecordHeader) {
                throw createParsingFailedException("header declaration is only allowed for record declaration", ctx.formalParameters());
            }
        }

        if (isSealed && isNonSealed) {
            throw createParsingFailedException("type cannot be defined with both `sealed` and `non-sealed`", nonSealedModifier.get());
        }

        if (isFinal && (isSealed || isNonSealed)) {
            throw createParsingFailedException("type cannot be defined with both " + (isSealed ? "`sealed`" : "`non-sealed`") + " and `final`", finalModifier.get());
        }

        if ((isAnnotation || isEnum) && (isSealed || isNonSealed)) {
            ModifierNode mn = isSealed ? sealedModifier.get() : nonSealedModifier.get();
            throw createParsingFailedException("modifier `" + mn.getText() + "` is not allowed for " + (isEnum ? "enum" : "annotation definition"), mn);
        }

        boolean hasPermits = asBoolean(ctx.PERMITS());
        if (!isSealed && hasPermits) {
            throw createParsingFailedException("only sealed type declarations should have `permits` clause", ctx);
        }

        int modifiers = modifierManager.getClassModifiersOpValue();

        boolean syntheticPublic = ((modifiers & Opcodes.ACC_SYNTHETIC) != 0);
        modifiers &= ~Opcodes.ACC_SYNTHETIC;

        ClassNode classNode, outerClass = this.classNodeStack.peek();

        if (isEnum) {
            classNode = EnumHelper.makeEnumNode(
                    asBoolean(outerClass) ? className : packageName + className,
                    modifiers,
                    null,
                    outerClass
            );
        } else if (outerClass != null) {
            classNode = new InnerClassNode(
                    outerClass,
                    outerClass.getName() + "$" + className,
                    modifiers,
                    ClassHelper.OBJECT_TYPE.getPlainNodeReference()
            );
        } else {
            classNode = new ClassNode(
                    packageName + className,
                    modifiers,
                    ClassHelper.OBJECT_TYPE.getPlainNodeReference()
            );
        }

        configureAST(classNode, ctx);
        classNode.setSyntheticPublic(syntheticPublic);
        classNode.setGenericsTypes(this.visitTypeParameters(ctx.typeParameters()));
        boolean isInterfaceWithDefaultMethods = (isInterface && this.containsDefaultOrPrivateMethods(ctx));

        if (isSealed) {
            AnnotationNode sealedAnnotationNode = makeAnnotationNode(Sealed.class);
            if (asBoolean(ctx.ps)) {
                ListExpression permittedSubclassesListExpression =
                        listX(Arrays.stream(this.visitTypeList(ctx.ps))
                                .map(ClassExpression::new)
                                .collect(Collectors.toList()));
                sealedAnnotationNode.setMember("permittedSubclasses", permittedSubclassesListExpression);
                configureAST(sealedAnnotationNode, ctx.PERMITS());
                sealedAnnotationNode.setNodeMetaData("permits", Boolean.TRUE);
            }
            classNode.addAnnotation(sealedAnnotationNode);
        } else if (isNonSealed) {
            classNode.addAnnotation(makeAnnotationNode(NonSealed.class));
        }
        if (asBoolean(ctx.TRAIT())) {
            classNode.addAnnotation(makeAnnotationNode(Trait.class));
        }
        classNode.addAnnotations(modifierManager.getAnnotations());
        if (isRecord && classNode.getAnnotations().stream().noneMatch(a ->
                        a.getClassNode().getName().equals(RECORD_TYPE_NAME))) {
            classNode.addAnnotation(new AnnotationNode(ClassHelper.makeWithoutCaching(RECORD_TYPE_NAME))); // TODO: makeAnnotationNode(RecordType.class)
        }

        if (isInterfaceWithDefaultMethods) {
            classNode.putNodeMetaData(IS_INTERFACE_WITH_DEFAULT_METHODS, Boolean.TRUE);
        }
        classNode.putNodeMetaData(CLASS_NAME, className);

        if (asBoolean(ctx.CLASS()) || asBoolean(ctx.TRAIT())) {
            if (asBoolean(ctx.scs)) {
                ClassNode[] scs = this.visitTypeList(ctx.scs);
                if (scs.length > 1) {
                    throw createParsingFailedException("Cannot extend multiple classes", ctx.EXTENDS());
                }
                classNode.setSuperClass(scs[0]);
            }
            classNode.setInterfaces(this.visitTypeList(ctx.is));
            this.checkUsingGenerics(classNode);

        } else if (isInterface) {
            classNode.setModifiers(classNode.getModifiers() | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | (outerClass != null ? Opcodes.ACC_STATIC : 0));
            classNode.setInterfaces(this.visitTypeList(ctx.scs));
            this.checkUsingGenerics(classNode);
            this.hackMixins(classNode);

        } else if (isEnum || isRecord) {
            if (isRecord) this.transformRecordHeaderToProperties(ctx, classNode);
            classNode.setInterfaces(this.visitTypeList(ctx.is));
            this.checkUsingGenerics(classNode);

        } else if (isAnnotation) {
            classNode.setModifiers(classNode.getModifiers() | Opcodes.ACC_ANNOTATION | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | (outerClass != null ? Opcodes.ACC_STATIC : 0));
            classNode.addInterface(ClassHelper.Annotation_TYPE);
            this.hackMixins(classNode);

        } else {
            throw createParsingFailedException("Unsupported class declaration: " + ctx.getText(), ctx);
        }

        this.classNodeStack.push(classNode);
        ctx.classBody().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
        this.visitClassBody(ctx.classBody());
        if (isRecord) {
            classNode.getFields().stream().filter(f -> !isTrue(f, IS_RECORD_GENERATED) && !f.isStatic()).findFirst()
                    .ifPresent(fn -> {
                        throw this.createParsingFailedException("Instance field is not allowed in `record`", fn);
                    });

            final List<Statement> objectInitializerStatements = classNode.getObjectInitializerStatements();
            if (asBoolean(objectInitializerStatements)) {
                throw this.createParsingFailedException("Instance initializer is not allowed in `record`", objectInitializerStatements.get(0));
            }
        }
        this.classNodeStack.pop();

        // The first element in classNodeList determines what GCL#parseClass for
        // example will return. So we have to ensure it won't be an inner class.
        if (outerClass == null) {
            this.addToClassNodeList(classNode);
        }
        this.groovydocManager.handle(classNode, ctx);

        return classNode;
    }