Skip to content

Commit

Permalink
Issue #5899: Add InterfaceImpliedModifierCheck
Browse files Browse the repository at this point in the history
  • Loading branch information
jodastephen committed Jun 27, 2018
1 parent ecd1b65 commit 7711035
Show file tree
Hide file tree
Showing 25 changed files with 1,117 additions and 0 deletions.
4 changes: 4 additions & 0 deletions config/checkstyle_checks.xml
Expand Up @@ -488,6 +488,10 @@
<!-- Modifiers -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<module name="InterfaceMemberImpliedModifier">
<!-- effectively the opposite of RedundantModifier, so output must be ignored -->
<property name="severity" value="ignore"/>
</module>

<!-- Naming Conventions -->
<module name="AbbreviationAsWordInName">
Expand Down
Expand Up @@ -654,6 +654,8 @@ private static void fillChecksFromModifierPackage() {
BASE_PACKAGE + ".checks.modifier.ModifierOrderCheck");
NAME_TO_FULL_MODULE_NAME.put("RedundantModifierCheck",
BASE_PACKAGE + ".checks.modifier.RedundantModifierCheck");
NAME_TO_FULL_MODULE_NAME.put("InterfaceMemberImpliedModifierCheck",
BASE_PACKAGE + ".checks.modifier.InterfaceMemberImpliedModifierCheck");
}

/**
Expand Down
@@ -0,0 +1,274 @@
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2018 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.modifier;

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;

/**
* Checks modifiers on interface members, ensuring that certain modifiers are
* explicitly specified even though they are actually redundant.
*
* <p>Methods on interfaces are {@code public} by default, however from Java 9
* they can also be {@code private}. This check provides the ability to enforce
* that {@code public} is not implicitly added by the compiler.
*
* <p>From Java 8, there are three types of methods on interfaces - static methods
* marked with {@code static}, default methods marked with {@code default} and
* abstract methods which do not have to be marked with anything.
* From Java 9, there are also private methods marked with {@code private}.
* This check provides the ability to enforce that {@code abstract} is not
* implicitly added by the compiler.
*
* <p>Fields on interfaces are always {@code public static final} and as such the
* compiler does not require these modifiers. This check provides the ability to
* enforce that these modifiers are not implicitly added by the compiler.
*
* <p>Nested types within an interface are always {@code public static} and as such the
* compiler does not require the {@code public static} modifiers. This check provides
* the ability to enforce that the {@code public} and {@code static} modifiers are
* not implicitly added by the compiler.
*
* <p>Rationale for these checks:
* Methods, fields and nested types are treated differently depending on whether
* they are part of an interface or part of a class. For example, by default methods
* are package-scoped on classes, but public on interfaces.
* However, from Java 8 onwards, interfaces have changed to be much more like abstract classes.
* Interfaces now have static and instance methods with code.
* Developers should not have to remember which modifiers are required and which are implicit.
* This check allows the simpler alternative approach to be adopted where the
* implied modifiers must always be coded explicitly.
*/
@StatelessCheck
public class InterfaceMemberImpliedModifierCheck
extends AbstractCheck {

/**
* A key is pointing to the warning message text in "messages.properties" file.
*/
public static final String MSG_KEY = "interface.implied.modifier";

/** Name for 'public' access modifier. */
private static final String PUBLIC_ACCESS_MODIFIER = "public";

/** Name for 'abstract' keyword. */
private static final String ABSTRACT_KEYWORD = "abstract";

/** Name for 'static' keyword. */
private static final String STATIC_KEYWORD = "static";

/** Name for 'final' keyword. */
private static final String FINAL_KEYWORD = "final";

/** Whether to check that interface methods are not implicitly {@code public}. */
private boolean violateImpliedPublicMethod = true;

/** Whether to check that interface methods are not implicitly {@code abstract}. */
private boolean violateImpliedAbstractMethod = true;

/** Whether to check that interface fields are not implicitly {@code public}. */
private boolean violateImpliedPublicField = true;

/** Whether to check that interface fields are not implicitly {@code static}. */
private boolean violateImpliedStaticField = true;

/** Whether to check that interface fields are not implicitly {@code final}. */
private boolean violateImpliedFinalField = true;

/** Whether to check that interface nested types are not implicitly {@code public}. */
private boolean violateImpliedPublicNested = true;

/** Whether to check that interface nested types are not implicitly {@code static}. */
private boolean violateImpliedStaticNested = true;

/**
* Setter that checks interface methods to make sure that {@code public} is not implied.
* @param violateImpliedPublicMethod
* True to perform the check, false to turn the check off.
*/
public void setViolateImpliedPublicMethod(boolean violateImpliedPublicMethod) {
this.violateImpliedPublicMethod = violateImpliedPublicMethod;
}

/**
* Setter that checks interface method to make sure that {@code abstract} is not implied.
* @param violateImpliedAbstractMethod
* True to perform the check, false to turn the check off.
*/
public void setViolateImpliedAbstractMethod(boolean violateImpliedAbstractMethod) {
this.violateImpliedAbstractMethod = violateImpliedAbstractMethod;
}

/**
* Setter that checks interface fields to make sure that {@code public} is not implied.
* @param violateImpliedPublicField
* True to perform the check, false to turn the check off.
*/
public void setViolateImpliedPublicField(boolean violateImpliedPublicField) {
this.violateImpliedPublicField = violateImpliedPublicField;
}

/**
* Setter that checks interface fields to make sure that {@code static} is not implied.
* @param violateImpliedStaticField
* True to perform the check, false to turn the check off.
*/
public void setViolateImpliedStaticField(boolean violateImpliedStaticField) {
this.violateImpliedStaticField = violateImpliedStaticField;
}

/**
* Setter that checks interface fields to make sure that {@code final} is not implied.
* @param violateImpliedFinalField
* True to perform the check, false to turn the check off.
*/
public void setViolateImpliedFinalField(boolean violateImpliedFinalField) {
this.violateImpliedFinalField = violateImpliedFinalField;
}

/**
* Setter that checks interface nested types to make sure that {@code public} is not implied.
* @param violateImpliedPublicNested
* True to perform the check, false to turn the check off.
*/
public void setViolateImpliedPublicNested(boolean violateImpliedPublicNested) {
this.violateImpliedPublicNested = violateImpliedPublicNested;
}

/**
* Setter that checks interface nested types to make sure that {@code static} is not implied.
* @param violateImpliedStaticNested
* True to perform the check, false to turn the check off.
*/
public void setViolateImpliedStaticNested(boolean violateImpliedStaticNested) {
this.violateImpliedStaticNested = violateImpliedStaticNested;
}

@Override
public int[] getDefaultTokens() {
return getAcceptableTokens();
}

@Override
public int[] getRequiredTokens() {
return getAcceptableTokens();
}

@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.METHOD_DEF,
TokenTypes.VARIABLE_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.CLASS_DEF,
TokenTypes.ENUM_DEF,
};
}

@Override
public void visitToken(DetailAST ast) {
if (isInterfaceMember(ast)) {
if (ast.getType() == TokenTypes.METHOD_DEF) {
processMethod(ast);
}
if (ast.getType() == TokenTypes.VARIABLE_DEF) {
processField(ast);
}
if (ast.getType() == TokenTypes.CLASS_DEF
|| ast.getType() == TokenTypes.INTERFACE_DEF
|| ast.getType() == TokenTypes.ENUM_DEF) {
processNestedType(ast);
}
}
}

/**
* Checks if current AST node is member of Interface, not of their subnodes.
* @param ast AST node
* @return true or false
*/
private static boolean isInterfaceMember(DetailAST ast) {
DetailAST parentTypeDef = ast.getParent();
if (parentTypeDef != null) {
parentTypeDef = parentTypeDef.getParent();
}
return parentTypeDef != null
&& parentTypeDef.getType() == TokenTypes.INTERFACE_DEF;
}

/**
* Check method on interface.
* @param ast method AST
*/
private void processMethod(DetailAST ast) {
final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
if (violateImpliedPublicMethod
&& modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
&& modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) == null) {
log(ast, MSG_KEY, PUBLIC_ACCESS_MODIFIER);
}
if (violateImpliedAbstractMethod
&& modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null
&& modifiers.findFirstToken(TokenTypes.LITERAL_DEFAULT) == null
&& modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
&& modifiers.findFirstToken(TokenTypes.ABSTRACT) == null) {
log(ast, MSG_KEY, ABSTRACT_KEYWORD);
}
}

/**
* Check field on interface.
* @param ast method AST
*/
private void processField(DetailAST ast) {
final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
if (violateImpliedPublicField
&& modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) == null) {
log(ast, MSG_KEY, PUBLIC_ACCESS_MODIFIER);
}
if (violateImpliedStaticField
&& modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
log(ast, MSG_KEY, STATIC_KEYWORD);
}
if (violateImpliedFinalField
&& modifiers.findFirstToken(TokenTypes.FINAL) == null) {
log(ast, MSG_KEY, FINAL_KEYWORD);
}
}

/**
* Check nested types on interface.
* @param ast method AST
*/
private void processNestedType(DetailAST ast) {
final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
if (violateImpliedPublicNested
&& modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) == null) {
log(ast, MSG_KEY, PUBLIC_ACCESS_MODIFIER);
}
if (violateImpliedStaticNested
&& modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
log(ast, MSG_KEY, STATIC_KEYWORD);
}
}

}
@@ -1,3 +1,4 @@
annotation.order=''{0}'' annotation modifier does not precede non-annotation modifiers.
mod.order=''{0}'' modifier out of order with the JLS suggestions.
redundantModifier=Redundant ''{0}'' modifier.
interface.implied.modifier=Implied modifier ''{0}'' should be explicit.
@@ -1,3 +1,4 @@
annotation.order=Annotation-Modifier ''{0}'' sollte vor den anderen Modifiern stehen.
mod.order=Modifier ''{0}'' weicht von der empfohlenen Modifier-Reihenfolge der Java Language Specification ab.
redundantModifier=Überflüssiger Modifier ''{0}''.
interface.implied.modifier=Impliziter Modifikator sollte explizit sein: ''{0}''
@@ -1,3 +1,4 @@
annotation.order=El modificador de anotación ''{0}'' no precede a los modificadores normales.
mod.order=Modificador ''{0}'' desordenado según las sugerencias de la JLS.
redundantModifier=Modificador ''{0}'' redundante.
interface.implied.modifier=El modificador impl\u00edcito debe ser expl\u00edcito: ''{0}''
@@ -1,3 +1,4 @@
annotation.order=''{0}'' huomautus muokkaaja ei synny ennen kuin merkintä määritteet.
mod.order=''{0}'' määrite rikkoo JLS:n suositusten mukaisesen järjestyksen.
redundantModifier=Tarpeeton määrite: ''{0}''
interface.implied.modifier=Johdettu modifioijan on oltava selke\u00e4: ''{0}''
@@ -1,3 +1,4 @@
annotation.order=Le modificateur d''annotation ''{0}'' ne précède pas les autres modificateurs.
mod.order=Le mot-clef ''{0}'' n''apparaît pas dans l''ordre préconisé par les JLS.
redundantModifier=Mot-clef ''{0}'' redondant.
interface.implied.modifier=Le modificateur implicite devrait \u00eatre explicite: ''{0}''
@@ -1,3 +1,4 @@
annotation.order=''{0}'' 注釈修飾子は、非注釈修飾子に先行していません。
mod.order=''{0}'' 修飾子が JLS 提案の順序に沿いません。
redundantModifier=冗長な ''{0}'' 修飾子です。
interface.implied.modifier=\u6697\u793A\u3055\u308C\u305F\u4FEE\u98FE\u5B50\u306F\u660E\u793A\u7684\u306B\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059: ''{0}''
@@ -1,3 +1,4 @@
annotation.order=A anotação ''{0}'' não deveria preceder modificadores que não são anotações.
mod.order=O modificador ''{0}'' está fora da ordem sugerida pela JLS.
redundantModifier=O modificador ''{0}'' é redundante.
interface.implied.modifier=O modificador impl\u00edcito deve ser expl\u00edcito: ''{0}''
Expand Up @@ -5,3 +5,4 @@ annotation.order = ''{0}'' anotasyon niteleyicisi, anotasyon olmayan niteleyicil
mod.order = ''{0}'' niteleyicisi, Java tarafından önerilen sırada değil.

redundantModifier = Gereksiz ''{0}'' niteleyicisi.
interface.implied.modifier=Z\u0131mni de\u011Fi\u015Ftirici aç\u0131k olmal\u0131d\u0131r: ''{0}''
@@ -1,3 +1,4 @@
annotation.order=注解 ''{0}'' 前不应有非注解修饰符。
mod.order=''{0}'' 修饰符顺序违反 JLS 建议.
redundantModifier=多余修饰符: ''{0}''
interface.implied.modifier=\u9690\u542B\u7684\u4FEE\u9970\u7B26\u5E94\u8BE5\u662F\u660E\u786E\u7684: ''{0}''

0 comments on commit 7711035

Please sign in to comment.