// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeserver.highlighting;

import com.intellij.codeInsight.UnhandledExceptions;
import com.intellij.java.codeserver.core.JavaPreviewFeatureUtil;
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
import com.intellij.java.syntax.parser.JavaKeywords;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.JavaVersionService;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.IncompleteModelUtil;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.refactoring.util.RefactoringChangeUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.function.Consumer;

import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.PREVIEW_API_USAGE;
import static java.util.Objects.requireNonNull;

/**
 * An internal visitor to gather error messages for Java sources. Should not be used directly.
 * Use {@link JavaErrorCollector} instead.
 */
final class JavaErrorVisitor extends JavaElementVisitor {
  private final @NotNull Consumer<JavaCompilationError<?, ?>> myErrorConsumer;
  private final @NotNull Project myProject;
  private final @NotNull PsiFile myPsiFile;
  private final @NotNull PsiElementFactory myFactory;
  private final @NotNull LanguageLevel myLanguageLevel;
  private final @NotNull AnnotationChecker myAnnotationChecker = new AnnotationChecker(this);
  final @NotNull ClassChecker myClassChecker = new ClassChecker(this);
  private final @NotNull RecordChecker myRecordChecker = new RecordChecker(this);
  private final @NotNull ImportChecker myImportChecker = new ImportChecker(this);
  final @NotNull GenericsChecker myGenericsChecker = new GenericsChecker(this);
  final @NotNull TypeChecker myTypeChecker = new TypeChecker(this);
  final @NotNull MethodChecker myMethodChecker = new MethodChecker(this);
  private final @NotNull ReceiverChecker myReceiverChecker = new ReceiverChecker(this);
  final @NotNull ControlFlowChecker myControlFlowChecker = new ControlFlowChecker(this);
  private final @NotNull FunctionChecker myFunctionChecker = new FunctionChecker(this);
  final @NotNull PatternChecker myPatternChecker = new PatternChecker(this);
  final @NotNull ModuleChecker myModuleChecker = new ModuleChecker(this);
  final @NotNull ModifierChecker myModifierChecker = new ModifierChecker(this);
  final @NotNull ExpressionChecker myExpressionChecker = new ExpressionChecker(this);
  private final @NotNull SwitchChecker mySwitchChecker = new SwitchChecker(this);
  private final @NotNull StatementChecker myStatementChecker = new StatementChecker(this);
  private final @NotNull LiteralChecker myLiteralChecker = new LiteralChecker(this);
  private final @Nullable PsiJavaModule myJavaModule;
  private final @NotNull JavaSdkVersion myJavaSdkVersion;
  private boolean myHasError; // true if myHolder.add() was called with HighlightInfo of >=ERROR severity. On each .visit(PsiElement) call this flag is reset. Useful to determine whether the error was already reported while visiting this PsiElement.

  JavaErrorVisitor(@NotNull PsiFile psiFile, @NotNull Consumer<JavaCompilationError<?, ?>> consumer) {
    myPsiFile = psiFile;
    myProject = psiFile.getProject();
    myLanguageLevel = PsiUtil.getLanguageLevel(psiFile);
    myErrorConsumer = consumer;
    myJavaModule = isApplicable(JavaFeature.MODULES) ? JavaPsiModuleUtil.findDescriptorByElement(psiFile) : null;
    myJavaSdkVersion = ObjectUtils
      .notNull(JavaVersionService.getInstance().getJavaSdkVersion(psiFile), JavaSdkVersion.fromLanguageLevel(myLanguageLevel));
    myFactory = JavaPsiFacade.getElementFactory(myProject);
  }

  void report(@NotNull JavaCompilationError<?, ?> error) {
    myHasError = true;
    if (ContainerUtil.exists(JavaErrorFilter.EP_NAME.getExtensionList(), ep -> ep.shouldSuppressError(myPsiFile, error))) return;
    myErrorConsumer.accept(error);
  }
  
  @Contract(pure = true)
  boolean isApplicable(@NotNull JavaFeature feature) {
    return feature.isSufficient(myLanguageLevel);
  }

  @NotNull PsiFile file() {
    return myPsiFile;
  }

  @NotNull Project project() {
    return myProject;
  }

  @Nullable PsiJavaModule javaModule() {
    return myJavaModule;
  }

  @NotNull PsiElementFactory factory() {
    return myFactory;
  }

  @NotNull LanguageLevel languageLevel() {
    return myLanguageLevel;
  }

  @NotNull JavaSdkVersion sdkVersion() {
    return myJavaSdkVersion;
  }

  @Contract(pure = true)
  boolean hasErrorResults() {
    return myHasError;
  }
  
  @Contract(pure = true)
  boolean isIncompleteModel() {
    return IncompleteModelUtil.isIncompleteModel(myPsiFile);
  }

  @Override
  public void visitElement(@NotNull PsiElement element) {
    super.visitElement(element);
    if (!(myPsiFile instanceof ServerPageFile)) {
      checkUnicodeBadCharacter(element);
    }
    myHasError = false;
  }

  private void checkUnicodeBadCharacter(@NotNull PsiElement element) {
    ASTNode node = element.getNode();
    if (node != null && node.getElementType() == TokenType.BAD_CHARACTER) {
      char c = element.textToCharArray()[0];
      report(JavaErrorKinds.ILLEGAL_CHARACTER.create(element, c));
    }
  }

  @Override
  public void visitErrorElement(@NotNull PsiErrorElement element) {
    super.visitErrorElement(element);
    if (JavaSyntaxErrorChecker.shouldHighlightErrorElement(element)) {
      report(JavaErrorKinds.SYNTAX_ERROR.create(element));
    }
  }

  @Override
  public void visitRecordComponent(@NotNull PsiRecordComponent recordComponent) {
    super.visitRecordComponent(recordComponent);
    if (!hasErrorResults()) myRecordChecker.checkRecordComponentWellFormed(recordComponent);
    if (!hasErrorResults()) myRecordChecker.checkRecordAccessorReturnType(recordComponent);
    if (!hasErrorResults()) myControlFlowChecker.checkRecordComponentInitialized(recordComponent);
  }

  @Override
  public void visitStatement(@NotNull PsiStatement statement) {
    super.visitStatement(statement);
    if (!hasErrorResults()) myStatementChecker.checkNotAStatement(statement);
  }

  @Override
  public void visitTryStatement(@NotNull PsiTryStatement statement) {
    super.visitTryStatement(statement);
    if (!hasErrorResults()) {
      UnhandledExceptions thrownTypes = UnhandledExceptions.fromTryStatement(statement);
      if (thrownTypes.hasUnresolvedCalls()) return;
      for (PsiParameter parameter : statement.getCatchBlockParameters()) {
        myStatementChecker.checkExceptionAlreadyCaught(parameter);
        if (!hasErrorResults()) myStatementChecker.checkExceptionThrownInTry(parameter, thrownTypes.exceptions());
      }
    }
  }

  @Override
  public void visitBreakStatement(@NotNull PsiBreakStatement statement) {
    super.visitBreakStatement(statement);
    if (!hasErrorResults()) myStatementChecker.checkBreakTarget(statement);
  }

  @Override
  public void visitYieldStatement(@NotNull PsiYieldStatement statement) {
    super.visitYieldStatement(statement);
    if (!hasErrorResults()) mySwitchChecker.checkYieldOutsideSwitchExpression(statement);
    if (!hasErrorResults()) {
      PsiExpression expression = statement.getExpression();
      if (expression != null) {
        mySwitchChecker.checkYieldExpressionType(expression);
      }
    }
  }

  @Override
  public void visitExpressionStatement(@NotNull PsiExpressionStatement statement) {
    super.visitExpressionStatement(statement);
    PsiElement parent = statement.getParent();
    if (parent instanceof PsiSwitchLabeledRuleStatement ruleStatement) {
      PsiSwitchBlock switchBlock = ruleStatement.getEnclosingSwitchBlock();
      if (switchBlock instanceof PsiSwitchExpression expr && !PsiPolyExpressionUtil.isPolyExpression(expr)) {
        mySwitchChecker.checkYieldExpressionType(statement.getExpression());
      }
    }
  }

  @Override
  public void visitContinueStatement(@NotNull PsiContinueStatement statement) {
    super.visitContinueStatement(statement);
    if (!hasErrorResults()) myStatementChecker.checkContinueTarget(statement);
  }

  @Override
  public void visitTypeElement(@NotNull PsiTypeElement type) {
    super.visitTypeElement(type);
    if (!hasErrorResults()) myTypeChecker.checkIllegalType(type);
    if (!hasErrorResults()) myTypeChecker.checkVarTypeApplicability(type);
    if (!hasErrorResults()) myTypeChecker.checkArrayType(type);
    if (!hasErrorResults()) myGenericsChecker.checkReferenceTypeUsedAsTypeArgument(type);
    if (!hasErrorResults()) myGenericsChecker.checkWildcardUsage(type);
    if (!hasErrorResults()) checkPreviewFeature(type);
  }

  @Override
  public void visitParameter(@NotNull PsiParameter parameter) {
    super.visitParameter(parameter);

    PsiElement parent = parameter.getParent();
    if (parent instanceof PsiParameterList && parameter.isVarArgs()) {
      if (!hasErrorResults()) checkFeature(parameter, JavaFeature.VARARGS);
      if (!hasErrorResults()) myMethodChecker.checkVarArgParameterWellFormed(parameter);
    }
    else if (parent instanceof PsiCatchSection) {
      if (!hasErrorResults() && parameter.getType() instanceof PsiDisjunctionType) {
        checkFeature(parameter, JavaFeature.MULTI_CATCH);
      }
      if (!hasErrorResults()) myTypeChecker.checkMustBeThrowable(parameter, parameter.getType());
      if (!hasErrorResults()) myStatementChecker.checkCatchTypeIsDisjoint(parameter);
      if (!hasErrorResults()) myGenericsChecker.checkCatchParameterIsClass(parameter);
    }
    else if (parent instanceof PsiForeachStatement forEach) {
      checkFeature(forEach, JavaFeature.FOR_EACH);
      if (!hasErrorResults()) myGenericsChecker.checkForEachParameterType(forEach, parameter);
    }
  }

  @Override
  public void visitAnnotation(@NotNull PsiAnnotation annotation) {
    super.visitAnnotation(annotation);
    if (!hasErrorResults()) checkFeature(annotation, JavaFeature.ANNOTATIONS);
    myAnnotationChecker.checkAnnotation(annotation);
  }
  
  @Override
  public void visitPackageStatement(@NotNull PsiPackageStatement statement) {
    super.visitPackageStatement(statement);
    if (!hasErrorResults()) myAnnotationChecker.checkPackageAnnotationContainingFile(statement);
    if (!hasErrorResults()) myClassChecker.checkPackageNotAllowedInImplicitClass(statement);
    if (isApplicable(JavaFeature.MODULES)) {
      if (!hasErrorResults()) myModuleChecker.checkPackageStatement(statement);
    }
  }

  @Override
  public void visitReceiverParameter(@NotNull PsiReceiverParameter parameter) {
    super.visitReceiverParameter(parameter);
    myReceiverChecker.checkReceiver(parameter);
  }

  @Override
  public void visitNameValuePair(@NotNull PsiNameValuePair pair) {
    super.visitNameValuePair(pair);
    myAnnotationChecker.checkNameValuePair(pair);
  }

  @Override
  public void visitEnumConstant(@NotNull PsiEnumConstant enumConstant) {
    super.visitEnumConstant(enumConstant);
    if (!hasErrorResults()) myClassChecker.checkEnumWithAbstractMethods(enumConstant);
    if (!hasErrorResults()) myExpressionChecker.checkUnhandledExceptions(enumConstant);
    if (!hasErrorResults()) {
      PsiClassType type = factory().createType(requireNonNull(enumConstant.getContainingClass()));
      myExpressionChecker.checkConstructorCall(type.resolveGenerics(), enumConstant, null);
    }
  }

  @Override
  public void visitTemplateExpression(@NotNull PsiTemplateExpression expression) {
    super.visitTemplateExpression(expression);
    if (!hasErrorResults()) myExpressionChecker.checkTemplateExpression(expression);
    if (!hasErrorResults()) myExpressionChecker.checkUnhandledExceptions(expression);
  }

  @Override
  public void visitTemplate(@NotNull PsiTemplate template) {
    super.visitTemplate(template);
    checkFeature(template, JavaFeature.STRING_TEMPLATES);
    if (hasErrorResults()) return;

    for (PsiExpression embeddedExpression : template.getEmbeddedExpressions()) {
      if (PsiTypes.voidType().equals(embeddedExpression.getType())) {
        report(JavaErrorKinds.STRING_TEMPLATE_VOID_NOT_ALLOWED_IN_EMBEDDED.create(embeddedExpression));
      }
    }
  }

  @Override
  public void visitNewExpression(@NotNull PsiNewExpression expression) {
    super.visitNewExpression(expression);
    PsiType type = expression.getType();
    PsiClass aClass = PsiUtil.resolveClassInType(type);
    if (aClass != null && !hasErrorResults()) myClassChecker.checkIllegalInstantiation(aClass, expression);
    if (aClass != null && !(type instanceof PsiArrayType) && !(type instanceof PsiPrimitiveType) && !hasErrorResults()) {
      myExpressionChecker.checkCreateInnerClassFromStaticContext(expression, aClass);
    }
    if (!hasErrorResults()) myClassChecker.checkAnonymousInheritFinal(expression);
    if (!hasErrorResults()) myClassChecker.checkAnonymousInheritProhibited(expression);
    if (!hasErrorResults()) myClassChecker.checkAnonymousSealedProhibited(expression);
    if (!hasErrorResults()) myExpressionChecker.checkQualifiedNew(expression, type, aClass);
    if (!hasErrorResults()) myExpressionChecker.checkUnhandledExceptions(expression);
    if (!hasErrorResults()) myExpressionChecker.checkNewExpression(expression, type);
    if (!hasErrorResults()) myGenericsChecker.checkTypeParameterInstantiation(expression);
    if (!hasErrorResults()) myGenericsChecker.checkGenericArrayCreation(expression, type);
    if (!hasErrorResults()) checkPreviewFeature(expression);
  }

  @Override
  public void visitComment(@NotNull PsiComment comment) {
    super.visitComment(comment);
    if (!hasErrorResults()) checkShebangComment(comment);
    if (!hasErrorResults()) checkUnclosedComment(comment);
    if (!hasErrorResults()) checkIllegalUnicodeEscapes(comment);
  }

  @Override
  public void visitDocComment(@NotNull PsiDocComment comment) {
    if (!hasErrorResults()) checkUnclosedComment(comment);
    if (!hasErrorResults()) checkIllegalUnicodeEscapes(comment);
  }

  @Override
  public void visitFragment(@NotNull PsiFragment fragment) {
    super.visitFragment(fragment);
    checkIllegalUnicodeEscapes(fragment);
    if (!hasErrorResults()) myLiteralChecker.checkFragmentError(fragment);
  }

  @Override
  public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) {
    super.visitLiteralExpression(expression);

    if (!hasErrorResults() &&
        expression.getParent() instanceof PsiCaseLabelElementList &&
        expression.textMatches(JavaKeywords.NULL)) {
      checkFeature(expression, JavaFeature.PATTERNS_IN_SWITCH);
    }

    if (!hasErrorResults()) checkIllegalUnicodeEscapes(expression);
    if (!hasErrorResults()) myLiteralChecker.getLiteralExpressionParsingError(expression);
  }

  @Override
  public void visitTypeParameterList(@NotNull PsiTypeParameterList list) {
    PsiTypeParameter[] typeParameters = list.getTypeParameters();
    if (typeParameters.length > 0) {
      checkFeature(list, JavaFeature.GENERICS);
      if (!hasErrorResults()) myGenericsChecker.checkTypeParametersList(list, typeParameters);
    }
  }

  @Override
  public void visitMethodReferenceExpression(@NotNull PsiMethodReferenceExpression expression) {
    visitElement(expression);
    checkFeature(expression, JavaFeature.METHOD_REFERENCES);
    PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
    if (toReportFunctionalExpressionProblemOnParent(parent)) return;
    PsiType functionalInterfaceType = expression.getFunctionalInterfaceType();
    if (functionalInterfaceType != null && !PsiTypesUtil.allTypeParametersResolved(expression, functionalInterfaceType)) return;

    JavaResolveResult[] results = expression.multiResolve(true);
    JavaResolveResult result = results.length == 1 ? results[0] : JavaResolveResult.EMPTY;
    PsiElement method = result.getElement();
    if (method instanceof PsiJvmMember member && !result.isAccessible()) {
      myModifierChecker.reportAccessProblem(expression, member, result);
      return;
    }
    if (!(parent instanceof DummyHolder) && !LambdaUtil.isValidLambdaContext(parent)) {
      report(JavaErrorKinds.METHOD_REFERENCE_NOT_EXPECTED.create(expression));
      return;
    }
    if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceQualifier(expression);
    if (!hasErrorResults()) {
      boolean resolvedButNonApplicable = results.length == 1 && results[0] instanceof MethodCandidateInfo methodInfo &&
                                         !methodInfo.isApplicable() &&
                                         functionalInterfaceType != null;
      if (results.length != 1 || resolvedButNonApplicable) {
        if (expression.isConstructor()) {
          PsiClass containingClass = PsiMethodReferenceUtil.getQualifierResolveResult(expression).getContainingClass();

          if (containingClass != null) {
            myClassChecker.checkIllegalInstantiation(containingClass, expression);
          }
        }
      }
    }
    if (!hasErrorResults()) myExpressionChecker.checkUnhandledExceptions(expression);
    if (!hasErrorResults()) {
      PsiElement qualifier = expression.getQualifier();
      if (qualifier instanceof PsiTypeElement typeElement) {
        PsiType psiType = typeElement.getType();
        myGenericsChecker.checkGenericArrayCreation(qualifier, psiType);
        if (hasErrorResults()) return;
      }
    }
    if (method instanceof PsiMethod psiMethod && psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
      if (!hasErrorResults() && psiMethod.hasTypeParameters()) {
        myGenericsChecker.checkParameterizedReferenceTypeArguments(method, expression, result.getSubstitutor());
      }
      PsiClass containingClass = psiMethod.getContainingClass();
      if (!hasErrorResults() && containingClass != null && containingClass.isInterface()) {
        myExpressionChecker.checkStaticInterfaceCallQualifier(expression, result, containingClass);
      }
    }
    if (!hasErrorResults()) myFunctionChecker.checkRawConstructorReference(expression);
    if (functionalInterfaceType != null) {
      if (!hasErrorResults()) myFunctionChecker.checkExtendsSealedClass(expression, functionalInterfaceType);
      boolean isFunctional = LambdaUtil.isFunctionalType(functionalInterfaceType);
      if (!hasErrorResults() && !isFunctional && 
          !(isIncompleteModel() && IncompleteModelUtil.isUnresolvedClassType(functionalInterfaceType))) {
        report(JavaErrorKinds.LAMBDA_NOT_FUNCTIONAL_INTERFACE.create(expression, functionalInterfaceType));
      }
      if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceContext(expression, functionalInterfaceType);
      if (!hasErrorResults()) myFunctionChecker.checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType);
    }
    if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceResolve(expression, results, functionalInterfaceType);
    if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceReturnType(expression, result, functionalInterfaceType);
    if (!hasErrorResults() && method instanceof PsiModifierListOwner) checkPreviewFeature(expression);
  }

  @Override
  public void visitThrowStatement(@NotNull PsiThrowStatement statement) {
    myExpressionChecker.checkUnhandledExceptions(statement);
    if (!hasErrorResults()) visitStatement(statement);
  }

  @Override
  public void visitModifierList(@NotNull PsiModifierList list) {
    super.visitModifierList(list);
    PsiElement parent = list.getParent();
    if (parent instanceof PsiMethod method) {
      if (!hasErrorResults()) {
        myClassChecker.checkConstructorCallsBaseClassConstructor(method);
      }
      if (!hasErrorResults()) myMethodChecker.checkMethodCanHaveBody(method);
      if (!hasErrorResults()) myMethodChecker.checkMethodMustHaveBody(method);
      if (!hasErrorResults()) myMethodChecker.checkStaticMethodOverride(method);
      MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY);
      PsiClass aClass = method.getContainingClass();
      if (!method.isConstructor()) {
        List<HierarchicalMethodSignature> superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures();
        if (!superMethodSignatures.isEmpty()) {
          if (!method.hasModifierProperty(PsiModifier.STATIC)) {
            if (!hasErrorResults()) myMethodChecker.checkMethodWeakerPrivileges(method, methodSignature, superMethodSignatures);
            if (!hasErrorResults()) myMethodChecker.checkMethodOverridesFinal(methodSignature, superMethodSignatures);
          }
          if (!hasErrorResults()) myMethodChecker.checkMethodIncompatibleReturnType(method, methodSignature, superMethodSignatures);
          if (aClass != null && !hasErrorResults()) {
            myMethodChecker.checkMethodIncompatibleThrows(method, methodSignature, superMethodSignatures, aClass);
          }
        }
      }
      if (!hasErrorResults() && aClass != null) {
        var error = myGenericsChecker.computeOverrideEquivalentMethodErrors(aClass).get(method);
        if (error != null) report(error);
      }
    }
    else if (parent instanceof PsiClass aClass && aClass.getModifierList() == list) {
      if (!hasErrorResults()) myClassChecker.checkDuplicateNestedClass(aClass);
      if (!hasErrorResults() && !(aClass instanceof PsiAnonymousClass)) {
        /* an anonymous class is highlighted in HighlightClassUtil.checkAbstractInstantiation()*/
        myClassChecker.checkClassMustBeAbstract(aClass);
      }
      if (!hasErrorResults()) {
        myClassChecker.checkClassDoesNotCallSuperConstructorOrHandleExceptions(aClass);
      }
      if (!hasErrorResults()) myClassChecker.checkCyclicInheritance(aClass);
      if (!hasErrorResults()) myMethodChecker.checkOverrideEquivalentInheritedMethods(aClass);
      if (!hasErrorResults()) {
        var error = myGenericsChecker.computeOverrideEquivalentMethodErrors(aClass).get(aClass);
        if (error != null) report(error);
      }
    }
  }

  @Override
  public void visitJavaFile(@NotNull PsiJavaFile file) {
    super.visitJavaFile(file);
    if (!hasErrorResults()) myClassChecker.checkImplicitClassWellFormed(file);
    if (!hasErrorResults()) myClassChecker.checkDuplicateClassesWithImplicit(file);
  }

  @Override
  public void visitJavaToken(@NotNull PsiJavaToken token) {
    super.visitJavaToken(token);

    IElementType type = token.getTokenType();
    if (!hasErrorResults() && type == JavaTokenType.TEXT_BLOCK_LITERAL) {
      checkFeature(token, JavaFeature.TEXT_BLOCKS);
    }

    if (!hasErrorResults()) {
      myImportChecker.checkExtraSemicolonBetweenImportStatements(token, type);
    }
    if (!hasErrorResults() && type == JavaTokenType.RBRACE && token.getParent() instanceof PsiCodeBlock block) {
      myControlFlowChecker.checkMissingReturn(block);
    }
  }

  @Override
  public void visitClassInitializer(@NotNull PsiClassInitializer initializer) {
    super.visitClassInitializer(initializer);
    if (!hasErrorResults()) myClassChecker.checkImplicitClassMember(initializer);
    if (!hasErrorResults()) myClassChecker.checkIllegalInstanceMemberInRecord(initializer);
    if (!hasErrorResults()) myClassChecker.checkThingNotAllowedInInterface(initializer);
    if (!hasErrorResults()) myClassChecker.checkInitializersInImplicitClass(initializer);
    if (!hasErrorResults()) myControlFlowChecker.checkUnreachableStatement(initializer.getBody());
    if (!hasErrorResults()) myControlFlowChecker.checkInitializerCompleteNormally(initializer);
  }

  @Override
  public void visitField(@NotNull PsiField field) {
    super.visitField(field);
    if (!hasErrorResults()) myClassChecker.checkIllegalInstanceMemberInRecord(field);
    if (!hasErrorResults()) myControlFlowChecker.checkFinalFieldInitialized(field);
  }

  @Override
  public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
    if (!hasErrorResults()) myExpressionChecker.checkConstructorCallProblems(expression);
    if (!hasErrorResults()) myExpressionChecker.checkSuperAbstractMethodDirectCall(expression);
    if (!hasErrorResults()) myClassChecker.checkEnumSuperConstructorCall(expression);
    if (!hasErrorResults()) myClassChecker.checkSuperQualifierType(expression);
    if (!hasErrorResults()) myExpressionChecker.checkMethodCall(expression);
    if (!hasErrorResults()) visitExpression(expression);
    if (!hasErrorResults()) checkPreviewFeature(expression);
  }

  @Override
  public void visitReturnStatement(@NotNull PsiReturnStatement statement) {
    super.visitReturnStatement(statement);
    if (!hasErrorResults()) myStatementChecker.checkReturnStatement(statement);
  }

  @Override
  public void visitReferenceParameterList(@NotNull PsiReferenceParameterList list) {
    super.visitReferenceParameterList(list);
    if (list.getTextLength() == 0) return;
    checkFeature(list, JavaFeature.GENERICS);
    if (!hasErrorResults()) {
      for (PsiTypeElement typeElement : list.getTypeParameterElements()) {
        if (typeElement.getType() instanceof PsiDiamondType) {
          checkFeature(list, JavaFeature.DIAMOND_TYPES);
        }
      }
    }
    if (!hasErrorResults()) myGenericsChecker.checkParametersAllowed(list);
    if (!hasErrorResults()) myGenericsChecker.checkParametersOnRaw(list);
  }

  @Override
  public void visitIdentifier(@NotNull PsiIdentifier identifier) {
    PsiElement parent = identifier.getParent();
    if (parent instanceof PsiVariable variable) {
      myStatementChecker.checkVariableAlreadyDefined(variable);
      if (variable.isUnnamed()) {
        checkFeature(variable, JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES);
        if (!hasErrorResults()) {
          myExpressionChecker.checkUnnamedVariableDeclaration(variable);
        }
      }
      else if (variable instanceof PsiField field) {
        myClassChecker.checkImplicitClassMember(field);
      }
    }
    else if (parent instanceof PsiClass aClass) {
      if (aClass.isAnnotationType()) {
        checkFeature(identifier, JavaFeature.ANNOTATIONS);
      }
      if (!hasErrorResults()) myClassChecker.checkClassAlreadyImported(aClass);
      if (!hasErrorResults()) myClassChecker.checkClassRestrictedKeyword(identifier);
      if (!hasErrorResults()) myGenericsChecker.checkUnrelatedConcrete(aClass);
      if (!hasErrorResults() && isApplicable(JavaFeature.EXTENSION_METHODS)) myMethodChecker.checkUnrelatedDefaultMethods(aClass);
    }
    else if (parent instanceof PsiMethod method) {
      myClassChecker.checkImplicitClassMember(method);
      if (method.isConstructor()) myMethodChecker.checkConstructorName(method);
      PsiClass aClass = method.getContainingClass();
      if (aClass != null) {
        myGenericsChecker.checkDefaultMethodOverridesMemberOfJavaLangObject(aClass, method);
      }
    }
    myExpressionChecker.checkUnderscore(identifier);
  }

  @Override
  public void visitKeyword(@NotNull PsiKeyword keyword) {
    super.visitKeyword(keyword);
    if (!hasErrorResults()) myClassChecker.checkStaticDeclarationInInnerClass(keyword);
    if (!hasErrorResults()) myTypeChecker.checkIllegalVoidType(keyword);
    PsiElement parent = keyword.getParent();
    if (parent instanceof PsiModifierList psiModifierList) {
      if (!hasErrorResults()) myModifierChecker.checkNotAllowedModifier(keyword, psiModifierList);
      if (!hasErrorResults()) myModifierChecker.checkIllegalModifierCombination(keyword, psiModifierList);
      PsiElement pParent = psiModifierList.getParent();
      String text = keyword.getText();
      if (PsiModifier.ABSTRACT.equals(text) && pParent instanceof PsiMethod psiMethod) {
        if (!hasErrorResults()) {
          myMethodChecker.checkAbstractMethodInConcreteClass(psiMethod, keyword);
        }
      }
      else if (pParent instanceof PsiEnumConstant) {
        report(JavaErrorKinds.ENUM_CONSTANT_MODIFIER.create(keyword));
      }
    }
  }

  @Override
  public void visitLabeledStatement(@NotNull PsiLabeledStatement statement) {
    super.visitLabeledStatement(statement);
    if (!hasErrorResults()) myStatementChecker.checkLabelWithoutStatement(statement);
    if (!hasErrorResults()) myStatementChecker.checkLabelAlreadyInUse(statement);
  }

  @Override
  public void visitSwitchExpression(@NotNull PsiSwitchExpression expression) {
    super.visitSwitchExpression(expression);
    mySwitchChecker.checkSwitchBlock(expression);
    if (!hasErrorResults()) checkFeature(expression.getFirstChild(), JavaFeature.SWITCH_EXPRESSION);
    if (!hasErrorResults()) mySwitchChecker.checkSwitchExpressionReturnTypeCompatible(expression);
    if (!hasErrorResults()) mySwitchChecker.checkSwitchExpressionHasResult(expression);
  }

  @Override
  public void visitSwitchStatement(@NotNull PsiSwitchStatement statement) {
    super.visitSwitchStatement(statement);
    mySwitchChecker.checkSwitchBlock(statement);
  }

  @Override
  public void visitModule(@NotNull PsiJavaModule module) {
    super.visitModule(module);
    if (!hasErrorResults()) checkFeature(module, JavaFeature.MODULES);
    if (!hasErrorResults()) myModuleChecker.checkFileName(module);
    if (!hasErrorResults()) myModuleChecker.checkFileDuplicates(module);
    if (!hasErrorResults()) myModuleChecker.checkDuplicateStatements(module);
    if (!hasErrorResults()) myModuleChecker.checkClashingReads(module);
    if (!hasErrorResults()) myModuleChecker.checkFileLocation(module);
  }

  @Override
  public void visitProvidesStatement(@NotNull PsiProvidesStatement statement) {
    super.visitProvidesStatement(statement);
    if (isApplicable(JavaFeature.MODULES)) {
      if (!hasErrorResults()) myModuleChecker.checkServiceImplementations(statement);
    }
  }

  @Override
  public void visitRequiresStatement(@NotNull PsiRequiresStatement statement) {
    super.visitRequiresStatement(statement);
    if (isApplicable(JavaFeature.MODULES)) {
      if (!hasErrorResults() && myLanguageLevel.isAtLeast(LanguageLevel.JDK_10)) myModuleChecker.checkModifiers(statement);
      if (!hasErrorResults()) myModuleChecker.checkModuleReference(statement);
    }
  }

  @Override
  public void visitPackageAccessibilityStatement(@NotNull PsiPackageAccessibilityStatement statement) {
    super.visitPackageAccessibilityStatement(statement);
    if (isApplicable(JavaFeature.MODULES)) {
      if (!hasErrorResults()) myModuleChecker.checkHostModuleStrength(statement);
      if (!hasErrorResults()) myModuleChecker.checkDuplicateModuleReferences(statement);
      if (!hasErrorResults()) myModuleChecker.checkPackageReference(statement);
    }
  }

  @Override
  public void visitModuleStatement(@NotNull PsiStatement statement) {
    super.visitModuleStatement(statement);
    if (!hasErrorResults()) checkPreviewFeature(statement);
  }

  @Override
  public void visitUsesStatement(@NotNull PsiUsesStatement statement) {
    super.visitUsesStatement(statement);
    if (isApplicable(JavaFeature.MODULES)) {
      if (!hasErrorResults()) myModuleChecker.checkServiceReference(statement.getClassReference());
    }
  }

  @Override
  public void visitImportModuleStatement(@NotNull PsiImportModuleStatement statement) {
    super.visitImportModuleStatement(statement);
    if (!hasErrorResults()) checkFeature(statement, JavaFeature.MODULE_IMPORT_DECLARATIONS);
    if (!hasErrorResults()) myImportChecker.checkImportModuleInModuleInfo(statement);
    if (!hasErrorResults()) myModuleChecker.checkModuleReference(statement);
  }

  @Override
