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 _ =>
}