def checkModifiers()

in scala/scala-impl/src/org/jetbrains/plugins/scala/annotator/modifiers/ModifierChecker.scala [42:333]


  def checkModifiers(modifierList: ScModifierList)
                    (implicit holder: ScalaAnnotationHolder): Unit = modifierList.getParent match {
    case owner: ScModifierListOwner =>
      val modifiers = mutable.HashSet.empty[ScalaModifier]

      def checkIllegalCombinations(modifierElement: PsiElement, modifier: ScalaModifier): Boolean = {
        val maybeIllegalModifier = IllegalCombinations.collectFirst {
          case (`modifier`, illegalModifier) if owner.hasModifierPropertyScala(illegalModifier.text()) => illegalModifier
        }.orElse {
          if (modifiers.add(modifier)) None else Some(modifier)
        }

        for {
          illegalModifier <- maybeIllegalModifier
        } {
          modifiers.add(illegalModifier)
          val message =
            if (modifier == illegalModifier) ScalaBundle.message("modifier.is.duplicated", modifier.text)
            else ScalaBundle.message("illegal.modifiers.combination", modifier.text, illegalModifier.text)
          createErrorWithQuickFix(message, modifierElement, owner)
        }

        maybeIllegalModifier.isEmpty
      }

      val modifierNodes = modifierList.getNode.getChildren(null)
      for (modifierNode <- modifierNodes) {
        modifierNode.getPsi match {
          case accessModifier: ScAccessModifier => // todo: check private with final or sealed combination.
            val maybeModifier = if (accessModifier.isPrivate) Some(Private) else if (accessModifier.isProtected) Some(Protected) else None
            maybeModifier.foreach { modifier =>
              checkIllegalCombinations(accessModifier, modifier)
              if (owner.getContext.is[ScBlock]) {
                createErrorWithQuickFix(
                  ScalaBundle.message("access.modifier.is.not.allowed.here", modifier.text()),
                  accessModifier,
                  owner,
                )
              }
            }
          case modifierPsi if modifierPsi.getNode.getElementType.is[ScalaModifierTokenType] =>
            modifierNode.getText match {
              case LAZY =>
                owner match {
                  case _: ScPatternDefinition => checkIllegalCombinations(modifierPsi, Lazy)
                  case _: ScParameter =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("lazy.modifier.is.not.allowed.with.param"),
                      modifierPsi,
                      owner,
                    )
                  case _: ScValueDeclaration =>
                    if (!modifierList.isInScala3File) {
                      createErrorWithQuickFix(
                        ScalaBundle.message("lazy.values.may.not.be.abstract"),
                        modifierPsi,
                        owner,
                      )
                    }
                  case _ =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("lazy.modifier.is.not.allowed.here"),
                      modifierPsi,
                      owner,
                    )
                }
              case FINAL =>
                owner match {
                  case d: ScDeclaration if !d.hasAnnotation("scala.native") =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("final.modifier.not.with.declarations"),
                      modifierPsi,
                      owner,
                    )
                  case _: ScTrait =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("final.modifier.not.with.trait"),
                      modifierPsi,
                      owner,
                    )
                  case _: ScClass => checkIllegalCombinations(modifierPsi, Final)
                  case _: ScObject =>
                    if (owner.scalaLanguageLevelOrDefault >= ScalaLanguageLevel.Scala_2_13) {
                      // since Scala 2.13 overriding objects is not possible anymore, so final makes no sense
                      createWarningWithQuickFix(
                        ScalaBundle.message("final.modifier.is.redundant.on.objects"),
                        modifierPsi,
                        owner,
                      )
                    }
                    checkIllegalCombinations(modifierPsi, Final)
                  case e: ScMember if e.getParent.is[ScTemplateBody, ScEarlyDefinitions] =>
                    val redundant = (e.containingClass, e) match {
                      case (_, valMember: ScPatternDefinition) if valMember.typeElement.isEmpty &&
                        valMember.pList.simplePatterns => false // constant value definition, see SCL-11500
                      case (cls, _) if cls.hasFinalModifier => true
                      case _ => false
                    }
                    if (redundant) {
                      if (checkIllegalCombinations(modifierPsi, Final)) {
                        createWarningWithQuickFix(
                          ScalaBundle.message("final.modifier.is.redundant.with.final.parents"),
                          modifierPsi,
                          owner,
                        )
                      }
                    } else {
                      checkIllegalCombinations(modifierPsi, Final)
                    }
                  case e: ScMember if e.isTopLevel =>
                    checkIllegalCombinations(modifierPsi, Final)
                  case e: ScClassParameter =>
                    if (PsiTreeUtil.getParentOfType(e, classOf[ScTypeDefinition]).hasFinalModifier) {
                      if (checkIllegalCombinations(modifierPsi, Final)) {
                        createWarningWithQuickFix(
                          ScalaBundle.message("final.modifier.is.redundant.with.final.parents"),
                          modifierPsi,
                          owner,
                        )
                      }
                    } else {
                      checkIllegalCombinations(modifierPsi, Final)
                    }
                  case _ =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("final.modifier.is.not.allowed.here"),
                      modifierPsi,
                      owner,
                    )
                }
              case SEALED =>
                owner match {
                  case _: ScClass | _: ScTrait | _: ScClassParameter => checkIllegalCombinations(modifierPsi, Sealed)
                  case _ =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("sealed.modifier.is.not.allowed.here"),
                      modifierPsi,
                      owner,
                    )
                }
              case ABSTRACT =>
                owner match {
                  case _: ScClass => checkIllegalCombinations(modifierPsi, Abstract)
                  case _: ScTrait => if (checkIllegalCombinations(modifierPsi, Abstract)) {
                    createWarningWithQuickFix(
                      ScalaBundle.message("abstract.modifier.redundant.fot.traits"),
                      modifierPsi,
                      owner,
                    )
                  }
                  case member: ScMember if !member.isInstanceOf[ScTemplateBody] &&
                    member.getParent.is[ScTemplateBody] && owner.hasModifierPropertyScala(OVERRIDE) =>
                    // 'abstract override' modifier only allowed for members of traits
                    if (!member.containingClass.is[ScTrait]) {
                      createErrorWithQuickFix(
                        ScalaBundle.message("abstract.override.modifier.is.not.allowed"),
                        modifierPsi,
                        owner,
                      )
                    } else {
                      checkIllegalCombinations(modifierPsi, Abstract)
                    }
                  case _ =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("abstract.modifier.is.not.allowed"),
                      modifierPsi,
                      owner,
                    )
                }
              case OVERRIDE =>
                owner match {
                  case o: ScObject if o.containingClass != null => //allowed
                  case _: ScTypeDefinition =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("override.modifier.is.not.allowed.for.classes"),
                      modifierPsi,
                      owner,
                    )
                  case member: ScMember if member.getParent.is[ScTemplateBody, ScEarlyDefinitions, ScExtensionBody] =>
                    checkIllegalCombinations(modifierPsi, Override)
                  case _: ScClassParameter =>
                    checkIllegalCombinations(modifierPsi, Override)
                  case _ =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("override.modifier.is.not.allowed"),
                      modifierPsi,
                      owner,
                    )
                }
              case IMPLICIT =>
                owner match {
                  case c@(_: ScClass | _: ScObject) =>
                    val onTopLevel = c.getContext match {
                      case file: ScalaFile if !file.isWorksheetFile => true
                      case _: ScPackaging => true
                      case _ => false
                    }
                    if (onTopLevel && !owner.isInScala3File) {
                      createErrorWithQuickFix(
                        ScalaBundle.message("implicit.modifier.cannot.be.used.for.top.level.objects"),
                        modifierPsi,
                        owner,
                      )
                    } else
                      c match {
                        case clazz: ScClass =>
                          val error = !hasPrimaryConstructorWithExactlyOneParameterInFirstClause(clazz)
                          if (error) {
                            createErrorWithQuickFix(
                              ScalaBundle.message("implicit.class.must.have.a.primary.constructor.with.one.argument"),
                              modifierPsi,
                              owner,
                            )
                          }
                          if (clazz.hasModifierPropertyScala(ABSTRACT)) {
                            createErrorWithQuickFix(
                              ScalaBundle.message("class.is.abstract.it.cannot.be.instantiated", clazz.name),
                              modifierPsi,
                              owner,
                            )
                          }
                        case _ =>
                      }
                  case _: ScTrait | _: ScTypeAlias =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("implicit.modifier.can.be.used.only.for"),
                      modifierPsi,
                      owner,
                    )
                  case _ => checkIllegalCombinations(modifierPsi, Implicit)
                }
              case OPEN =>
                val (isRedundant, isValidUsage) = owner match {
                  case c: ScClass => (c.hasModifierPropertyScala(ABSTRACT), true)
                  case _: ScTrait => (true, true)
                  //note: compiler doesn't show warning for `open object` but it's probably a bug, cause it doesn't make any sense
                  case _: ScObject => (true, true)
                  case _ => (false, false)
                }
                if (isRedundant) {
                  createWarningWithQuickFix(
                    ScalaBundle.message("modifier.is.redundant.for.this.definition", OPEN),
                    modifierPsi,
                    owner,
                  )
                }
                else if (!isValidUsage) {
                  createErrorWithQuickFix(
                    ScalaBundle.message("only.classes.can.be.open"),
                    modifierPsi,
                    owner,
                  )
                }
                else {
                  checkIllegalCombinations(modifierPsi, Open)
                }
              case OPAQUE =>
                owner match {
                  case _: ScTypeAlias => //ok
                  case _ =>
                    createErrorWithQuickFix(
                      ScalaBundle.message("opaque.modifier.allowed.only.for.type.aliases"),
                      modifierPsi,
                      owner,
                    )
                }
              case INTO =>
                val allowed = owner match {
                  case alias: ScTypeAliasDefinition if alias.isOpaque => true
                  case _: ScObject => false
                  case _: ScClass | _: ScTrait | _: ScEnum => true
                  case _ => false
                }

                if (!allowed) {
                  createErrorWithQuickFix(
                    ScalaBundle.message("into.modifier.is.only.allowed.on"),
                    modifierPsi,
                    owner,
                  )
                }
              case other =>
                val otherModifier = ScalaModifier.byText(other)
                if (otherModifier != null) {
                  checkIllegalCombinations(modifierPsi, otherModifier)
                }
            }
          case _ => //e.g. whitespace
        }
      }
    case _ =>
  }