| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.java.metrics.hints; |
| |
| import com.sun.source.tree.AssertTree; |
| import com.sun.source.tree.BinaryTree; |
| import com.sun.source.tree.BlockTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.DoWhileLoopTree; |
| import com.sun.source.tree.EnhancedForLoopTree; |
| import com.sun.source.tree.ForLoopTree; |
| import com.sun.source.tree.IfTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.ReturnTree; |
| import com.sun.source.tree.StatementTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.UnaryTree; |
| import com.sun.source.tree.WhileLoopTree; |
| import com.sun.source.util.TreePath; |
| import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner; |
| import java.util.Collection; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.TypeElement; |
| import org.netbeans.spi.editor.hints.ErrorDescription; |
| import org.netbeans.spi.java.hints.BooleanOption; |
| import org.netbeans.spi.java.hints.ErrorDescriptionFactory; |
| import org.netbeans.spi.java.hints.Hint; |
| import org.netbeans.spi.java.hints.HintContext; |
| import org.netbeans.spi.java.hints.IntegerOption; |
| import org.netbeans.spi.java.hints.TriggerPattern; |
| import org.netbeans.spi.java.hints.TriggerPatterns; |
| import org.netbeans.spi.java.hints.TriggerTreeKind; |
| import org.netbeans.spi.java.hints.UseOptions; |
| import org.openide.util.NbBundle; |
| |
| import static org.netbeans.modules.java.metrics.hints.Bundle.*; |
| |
| |
| /** |
| * Inspections based on metrics computed for individual methods. |
| * |
| * @author sdedic |
| */ |
| @NbBundle.Messages({ |
| "# {0} - method name", |
| "# {1} - cyclomatic complexity of the method", |
| "TEXT_MethodTooComplex=The method ''{0}'' is too complex; cyclomatic complexity: {1}", |
| "# {0} - method name", |
| "# {1} - maximum depth of statements in method", |
| "TEXT_MethodTooDeepNesting=Method ''{0}'' contains too deep statement structure: {1}", |
| "# {0} - method name", |
| "# {1} - number of lines in method", |
| "TEXT_MethodTooLongLines=Method ''{0}'' is too long: {1} lines", |
| "# {0} - method name", |
| "# {1} - number of lines in method", |
| "TEXT_MethodTooLongStatements=Method ''{0}'' is too long: {1} statements", |
| "# {0} - method name", |
| "# {1} - number of exceptions declared by the method", |
| "TEXT_MethodTooManyExceptions=Method ''{0}'' declares too many exceptions: {1}", |
| "# {0} - method name", |
| "# {1} - number of parameters declared by the method", |
| "TEXT_MethodTooManyParameters=Method ''{0}'' takes too many parameters: {1}", |
| "# {0} - method name", |
| "# {1} - number of return points", |
| "TEXT_MethodMultipleReturns=Method ''{0}'' has multiple return points: {1}", |
| "# {0} - method name", |
| "# {1} - number of negations", |
| "TEXT_MethodMultipleNegations=Method ''{0}'' contains too many negations: {1}", |
| "# {0} - method name", |
| "# {1} - number of loops", |
| "TEXT_MethodMultipleLoops=Method ''{0}'' contains {1} loops", |
| "# {0} - method name", |
| "# {1} - number of dependencies", |
| "TEXT_MethodTooCoupled=Method ''{0}'' is too coupled. References {1} types", |
| |
| "# {0} - cyclomatic complexity of the method", |
| "TEXT_ConstructorTooComplex=Constructor is too complex; cyclomatic complexity: {0}", |
| "# {0} - maximum depth of statements in method", |
| "TEXT_ConstructorTooDeepNesting=Constructor contains too deep statement structure: {0}", |
| "# {0} - number of lines in method", |
| "TEXT_ConstructorTooLongLines=Constructor is too long: {0} lines", |
| "# {0} - number of lines in method", |
| "TEXT_ConstructorTooLongStatements=Constructor is too long: {0} statements", |
| "# {0} - number of exceptions declared by the method", |
| "TEXT_ConstructorTooManyExceptions=Constructor declares too many exceptions: {0}", |
| "# {0} - number of parameters declared by the method", |
| "TEXT_ConstructorTooManyParameters=Constructor takes too many parameters: {0}", |
| "# {0} - number of return points", |
| "TEXT_ConstructorMultipleReturns=Constructor has multiple return points: {0}", |
| "# {0} - number of negations", |
| "TEXT_ConstructorMultipleNegations=Constructor contains too many negations: {0}", |
| "# {0} - number of loops", |
| "TEXT_ConstructorMultipleLoops=Constructor contains {0} loops", |
| "# {0 - number of dependencies", |
| "TEXT_ConstructorTooCoupled=Constructor is too coupled. References {0} types" |
| }) |
| public class MethodMetrics { |
| static final int DEFAULT_COMPLEXITY_LIMIT = 10; |
| static final int DEFAULT_NESTING_LIMIT = 6; |
| static final int DEFAULT_LINES_LIMIT = 60; |
| static final int DEFAULT_EXCEPTIONS_LIMIT = 3; |
| static final int DEFAULT_STATEMENTS_LIMIT = 30; |
| static final int DEFAULT_METHOD_PARAMETERS_LIMIT = 8; |
| static final int DEFAULT_CTOR_PARAMETERS_LIMIT = 12; |
| static final boolean DEFAULT_IGNORE_RETURN_GUARDS = true; |
| static final boolean DEFAULT_IGNORE_EQUALS = true; |
| static final int DEFAULT_RETURN_LIMIT = 2; |
| static final int DEFAULT_NEGATIONS_LIMIT = 3; |
| static final boolean DEFAULT_NEGATIONS_IGNORE_ASSERT = true; |
| static final boolean DEFAULT_NEGATIONS_IGNORE_EQUALS = true; |
| static final boolean DEFAULT_COUPLING_IGNORE_JAVA = true; |
| static final int DEFAULT_LOOPS_LIMIT = 3; |
| static final int DEFAULT_COUPLING_LIMIT = 15; |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodComplexityLimit", |
| tooltip = "#OPTDESC_MethodComplexityLimit", |
| maxValue = 1000, |
| step = 1, |
| defaultValue = DEFAULT_COMPLEXITY_LIMIT |
| ) |
| public static final String OPTION_COMPLEXITY_TRESHOLD = "metrics.method.complexity.limit"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodDepthLimit", |
| tooltip = "#OPTDESC_MethodDepthLimit", |
| maxValue = 100, |
| step = 1, |
| defaultValue = DEFAULT_NESTING_LIMIT |
| ) |
| public static final String OPTION_NESTING_LIMIT = "metrics.method.depth.limit"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodLinesLimit", |
| tooltip = "#OPTDESC_MethodLinesLimit", |
| maxValue = 60, |
| step = 50, |
| defaultValue = DEFAULT_LINES_LIMIT |
| ) |
| public static final String OPTION_LINES_LIMIT = "metrics.method.lines.limit"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodStatementsLimit", |
| tooltip = "#OPTDESC_MethodStatementsLimit", |
| maxValue = 30, |
| step = 5, |
| defaultValue = DEFAULT_LINES_LIMIT |
| ) |
| public static final String OPTION_STATEMENTS_LIMIT = "metrics.method.statements.limit"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodExceptionsLimit", |
| tooltip = "#OPTDESC_MethodExceptionsLimit", |
| maxValue = 50, |
| minValue = 1, |
| step = 1, |
| defaultValue = DEFAULT_EXCEPTIONS_LIMIT |
| ) |
| public static final String OPTION_EXCEPTIONS_LIMIT = "metrics.method.exceptions.limit"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodParametersLimit", |
| tooltip = "#OPTDESC_MethodParametersLimit", |
| maxValue = 100, |
| minValue = 2, |
| step = 1, |
| defaultValue = DEFAULT_METHOD_PARAMETERS_LIMIT |
| ) |
| public static final String OPTION_METHOD_PARAMETERS_LIMIT = "metrics.method.parameters.limit"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_CtorParametersLimit", |
| tooltip = "#OPTDESC_CtorParametersLimit", |
| maxValue = 100, |
| minValue = 2, |
| step = 1, |
| defaultValue = DEFAULT_CTOR_PARAMETERS_LIMIT |
| ) |
| public static final String OPTION_CTOR_PARAMETERS_LIMIT = "metrics.constructor.parameters.limit"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodReturnLimit", |
| tooltip = "#OPTDESC_MethodReturnLimit", |
| maxValue = 100, |
| minValue = 1, |
| step = 1, |
| defaultValue = DEFAULT_RETURN_LIMIT |
| ) |
| public static final String OPTION_RETURN_LIMIT = "metrics.method.return.limit"; // NOI18N |
| |
| @BooleanOption( |
| displayName = "#OPTNAME_MethodIgnoreReturnGuards", |
| tooltip = "#OPTDESC_MethodIgnoreReturnGuards", |
| defaultValue = DEFAULT_IGNORE_RETURN_GUARDS |
| ) |
| public static final String OPTION_RETURN_IGNORE_GUARDS = "metrics.method.returns.ignoreguards"; // NOI18N |
| |
| @BooleanOption( |
| displayName = "#OPTNAME_MethodIgnoreReturnEquals", |
| tooltip = "#OPTDESC_MethodIgnoreReturnEquals", |
| defaultValue = DEFAULT_IGNORE_EQUALS |
| ) |
| public static final String OPTION_RETURN_IGNORE_EQUALS = "metrics.method.returns.ignoreequals"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodNegationsLimit", |
| tooltip = "#OPTDESC_MethodNegationsLimit", |
| maxValue = 100, |
| step = 1, |
| defaultValue = DEFAULT_NEGATIONS_LIMIT |
| ) |
| public static final String OPTION_NEGATIONS_LIMIT = "metrics.method.negations.limit"; // NOI18N |
| |
| @BooleanOption( |
| displayName = "#OPTNAME_MethodNegationsIgnoreEquals", |
| tooltip = "#OPTDESC_MethodNegationsIgnoreEquals", |
| defaultValue = DEFAULT_NEGATIONS_IGNORE_EQUALS |
| ) |
| public static final String OPTION_NEGATIONS_IGNORE_EQUALS = "metrics.method.negations.ignoreequals"; // NOI18N |
| |
| @BooleanOption( |
| displayName = "#OPTNAME_MethodNegationsIgnoreAsserts", |
| tooltip = "#OPTDESC_MethodNegationsIgnoreAsserts", |
| defaultValue = DEFAULT_NEGATIONS_IGNORE_ASSERT |
| ) |
| public static final String OPTION_NEGATIONS_IGNORE_ASSERT = "metrics.method.negations.ignoreassert"; // NOI18N |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodLoopsLimit", |
| tooltip = "#OPTDESC_MethodLoopsLimit", |
| maxValue = 100, |
| step = 1, |
| defaultValue = DEFAULT_LOOPS_LIMIT |
| ) |
| public static final String OPTION_LOOPS_LIMIT = "metrics.method.loops.limit"; // NOI18N |
| |
| |
| @IntegerOption( |
| displayName = "#OPTNAME_MethodCouplingLimit", |
| tooltip = "#OPTDESC_MethodCouplingLimit", |
| maxValue = 1000, |
| step = 1, |
| defaultValue = DEFAULT_COUPLING_LIMIT |
| ) |
| public static final String OPTION_COUPLING_LIMIT = "metrics.method.coupling.limit"; // NOI18N |
| |
| @BooleanOption( |
| displayName = "#OPTNAME_MethodCouplingIgnoreJava", |
| tooltip = "#OPTDESC_MethodCouplingIgnoreJava", |
| defaultValue = DEFAULT_COUPLING_IGNORE_JAVA |
| ) |
| public static final String OPTION_COUPLING_IGNORE_JAVA = "metrics.method.coupling.nojava"; // NOI18N |
| |
| public static final String OPTION_COUPLING_IGNORE_LIBS = "metrics.method.coupling.nolibraries"; // NOI18N |
| |
| private static boolean methodOrConstructor(HintContext ctx) { |
| Element el = ctx.getInfo().getTrees().getElement(ctx.getPath()); |
| return el.getKind() == ElementKind.CONSTRUCTOR; |
| } |
| |
| @Hint(category = "metrics", |
| displayName = "#DN_MethodTooComplex", |
| description = "#DESC_MethodTooComplex", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @TriggerTreeKind(Tree.Kind.METHOD) |
| @UseOptions(value = { OPTION_COMPLEXITY_TRESHOLD }) |
| public static ErrorDescription methodTooComplex(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| CyclomaticComplexityVisitor v = new CyclomaticComplexityVisitor(); |
| v.scan(ctx.getPath(), v); |
| int complexity = v.getComplexity(); |
| |
| int treshold = ctx.getPreferences().getInt(OPTION_COMPLEXITY_TRESHOLD, DEFAULT_COMPLEXITY_LIMIT); |
| if (complexity <= treshold) { |
| return null; |
| } |
| return ErrorDescriptionFactory.forName(ctx, t, |
| methodOrConstructor(ctx) ? |
| TEXT_ConstructorTooComplex(complexity) : |
| TEXT_MethodTooComplex(method.getName().toString(), complexity) |
| ); |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_MethodTooDeepNesting", |
| description = "#DESC_MethodTooDeepNesting", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @TriggerTreeKind(Tree.Kind.METHOD) |
| @UseOptions(value = { OPTION_NESTING_LIMIT }) |
| public static ErrorDescription tooDeepNesting(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| DepthVisitor v = new DepthVisitor(); |
| v.scan(ctx.getPath(), null); |
| |
| int depth = v.getDepth(); |
| int treshold = ctx.getPreferences().getInt(OPTION_NESTING_LIMIT, DEFAULT_NESTING_LIMIT); |
| if (depth <= treshold) { |
| return null; |
| } |
| return ErrorDescriptionFactory.forName(ctx, t, |
| methodOrConstructor(ctx) ? |
| TEXT_ConstructorTooDeepNesting(depth) : |
| TEXT_MethodTooDeepNesting(method.getName().toString(), depth) |
| ); |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_MethodTooLong", |
| description = "#DESC_MethodTooLong", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @TriggerTreeKind(Tree.Kind.METHOD) |
| @UseOptions({ OPTION_LINES_LIMIT, OPTION_STATEMENTS_LIMIT }) |
| public static ErrorDescription tooLong(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| NCLOCVisitor v = new NCLOCVisitor(ctx.getInfo().getSnapshot().getText(), ctx.getInfo().getTrees().getSourcePositions()); |
| v.scan(ctx.getPath(), null); |
| |
| int count = v.getLineCount(); |
| int treshold = ctx.getPreferences().getInt(OPTION_LINES_LIMIT, DEFAULT_LINES_LIMIT); |
| if (count > treshold) { |
| return ErrorDescriptionFactory.forName(ctx, t, |
| methodOrConstructor(ctx) ? |
| TEXT_ConstructorTooLongLines(count) : |
| TEXT_MethodTooLongLines(method.getName().toString(), count) |
| ); |
| } |
| count = v.getStatementCount(); |
| treshold = ctx.getPreferences().getInt(OPTION_STATEMENTS_LIMIT, DEFAULT_STATEMENTS_LIMIT); |
| if (count > treshold) { |
| return ErrorDescriptionFactory.forName(ctx, t, |
| methodOrConstructor(ctx) ? |
| TEXT_ConstructorTooLongStatements(count) : |
| TEXT_MethodTooLongStatements(method.getName().toString(), count) |
| ); |
| } else { |
| return null; |
| } |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_MethodTooManyExceptions", |
| description = "#DESC_MethodTooManyExceptions", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @UseOptions(value = { OPTION_EXCEPTIONS_LIMIT }) |
| @TriggerPatterns({ |
| @TriggerPattern("$modifiers$ <$typeParams$> $returnType $name($args$) throws $thrown1, $thrown2$ { $body$; }"), |
| @TriggerPattern("$modifiers$ <$typeParams$> $name($args$) throws $thrown1, $thrown2$ { $body$; }"), |
| }) |
| public static ErrorDescription tooManyExceptions(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| Collection<? extends TreePath> exc2 = ctx.getMultiVariables().get("$thrown2$"); // NOI18N |
| |
| int limit = ctx.getPreferences().getInt(OPTION_EXCEPTIONS_LIMIT, DEFAULT_EXCEPTIONS_LIMIT); |
| int count = exc2 == null ? 1 :exc2.size() + 1; |
| if (count <= limit) { |
| return null; |
| } |
| Element el = ctx.getInfo().getTrees().getElement(ctx.getPath()); |
| return ErrorDescriptionFactory.forName(ctx, t, |
| methodOrConstructor(ctx) ? |
| TEXT_ConstructorTooManyExceptions(count) : |
| TEXT_MethodTooManyExceptions(method.getName().toString(), count) |
| ); |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_MethodTooManyParameters", |
| description = "#DESC_MethodTooManyParameters", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @UseOptions(value = { OPTION_METHOD_PARAMETERS_LIMIT }) |
| @TriggerPattern("$modifiers$ <$typeParams$> $returnType $name($args1, $arg2, $args$) throws $whatever$ { $body$; }") |
| public static ErrorDescription tooManyParameters(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| |
| Collection<? extends TreePath> args = ctx.getMultiVariables().get("$args$"); // NOI18N |
| int limit = ctx.getPreferences().getInt(OPTION_METHOD_PARAMETERS_LIMIT, DEFAULT_METHOD_PARAMETERS_LIMIT); |
| int count = args.size() + 2; |
| if (count <= limit) { |
| return null; |
| } |
| return ErrorDescriptionFactory.forName(ctx, t, |
| methodOrConstructor(ctx) ? |
| TEXT_ConstructorTooManyParameters(count) : |
| TEXT_MethodTooManyParameters(method.getName().toString(), count) |
| ); |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_CtorTooManyParameters", |
| description = "#DESC_CtorTooManyParameters", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @UseOptions(value = { OPTION_METHOD_PARAMETERS_LIMIT }) |
| @TriggerPattern("$modifiers$ <$typeParams$> $name($args1, $arg2, $args$) throws $whatever$ { $body$; }") |
| public static ErrorDescription tooManyParametersCtor(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| |
| Collection<? extends TreePath> args = ctx.getMultiVariables().get("$args$"); // NOI18N |
| int limit = ctx.getPreferences().getInt(OPTION_METHOD_PARAMETERS_LIMIT, DEFAULT_METHOD_PARAMETERS_LIMIT); |
| int count = args.size() + 2; |
| if (count <= limit) { |
| return null; |
| } |
| return ErrorDescriptionFactory.forName(ctx, t, |
| TEXT_MethodTooManyParameters(method.getName().toString(), count) |
| ); |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_MethodMultipleReturns", |
| description = "#DESC_MethodMultipleReturns", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @UseOptions({ OPTION_RETURN_LIMIT, OPTION_RETURN_IGNORE_EQUALS, OPTION_RETURN_IGNORE_GUARDS }) |
| @TriggerTreeKind(Tree.Kind.METHOD) |
| public static ErrorDescription multipleReturnPoints(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| |
| boolean ignoreEquals = ctx.getPreferences().getBoolean(OPTION_RETURN_IGNORE_EQUALS, DEFAULT_IGNORE_EQUALS); |
| if (ignoreEquals && method.getName().contentEquals("equals")) { // NOI18N |
| return null; |
| } |
| |
| ReturnCountVisitor v = new ReturnCountVisitor( |
| ctx.getPreferences().getBoolean(OPTION_RETURN_IGNORE_GUARDS, DEFAULT_IGNORE_RETURN_GUARDS) |
| ); |
| v.scan(ctx.getPath(), null); |
| |
| int count = v.getReturnCount(); |
| int limit = ctx.getPreferences().getInt(OPTION_RETURN_LIMIT, DEFAULT_RETURN_LIMIT); |
| |
| if (count > limit) { |
| return ErrorDescriptionFactory.forName(ctx, t, |
| TEXT_MethodMultipleReturns(method.getName().toString(), count) |
| ); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * The visitor will ignore returns, which are the *sole* statement in a if-branch. |
| * Such branches are considered to be guards, which abort further processing. |
| */ |
| private static class ReturnCountVisitor extends ErrorAwareTreePathScanner { |
| /** |
| * Suppressed in local classes |
| */ |
| private boolean suppress; |
| |
| private int returnCount; |
| |
| /** |
| * If true, ignores guard returns |
| */ |
| private final boolean ignoreGuards; |
| |
| public ReturnCountVisitor(boolean ignoreGuards) { |
| this.ignoreGuards = ignoreGuards; |
| } |
| |
| public int getReturnCount() { |
| return returnCount; |
| } |
| |
| @Override |
| public Object visitClass(ClassTree node, Object p) { |
| boolean s = this.suppress; |
| this.suppress = true; |
| Object o = super.visitClass(node, p); |
| this.suppress = s; |
| return o; |
| } |
| |
| @Override |
| public Object visitReturn(ReturnTree node, Object p) { |
| TreePath path = getCurrentPath(); |
| TreePath parentPath = path.getParentPath(); |
| if (suppress) { |
| return super.visitReturn(node, p); |
| } |
| if (ignoreGuards && parentPath != null) { |
| Tree parentTree = parentPath.getLeaf(); |
| TreePath branchPath = path; |
| while (parentTree.getKind() == Tree.Kind.BLOCK) { |
| branchPath = parentPath; |
| parentPath = parentPath.getParentPath(); |
| parentTree = parentPath.getLeaf(); |
| } |
| if (parentTree.getKind() == Tree.Kind.IF) { |
| IfTree ifTree = (IfTree)parentTree; |
| StatementTree trueTree = ifTree.getThenStatement() == branchPath.getLeaf() ? |
| ifTree.getThenStatement() : ifTree.getElseStatement(); |
| if (trueTree == node) { |
| return super.visitReturn(node, p); |
| } |
| if (trueTree.getKind() == Tree.Kind.BLOCK) { |
| BlockTree bt = (BlockTree)trueTree; |
| if (bt.getStatements().size() == 1) { |
| return super.visitReturn(node, p); |
| } |
| } |
| } |
| } |
| returnCount++; |
| return super.visitReturn(node, p); |
| } |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_MethodMultipleNegations", |
| description = "#DESC_MethodMultipleNegations", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @TriggerTreeKind(Tree.Kind.METHOD) |
| @UseOptions(value = { OPTION_NEGATIONS_IGNORE_ASSERT, OPTION_NEGATIONS_IGNORE_EQUALS, OPTION_NEGATIONS_LIMIT }) |
| public static ErrorDescription multipleNegations(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| |
| boolean ignoreEquals = ctx.getPreferences().getBoolean(OPTION_NEGATIONS_IGNORE_EQUALS, DEFAULT_IGNORE_EQUALS); |
| if (ignoreEquals && method.getName().contentEquals("equals")) { // NOI18N |
| return null; |
| } |
| boolean ignoreAsserts = ctx.getPreferences().getBoolean(OPTION_NEGATIONS_IGNORE_ASSERT, DEFAULT_NEGATIONS_IGNORE_ASSERT); |
| |
| NegationsVisitor v = new NegationsVisitor(ignoreAsserts); |
| v.scan(ctx.getPath(), null); |
| int limit = ctx.getPreferences().getInt(OPTION_NEGATIONS_LIMIT, DEFAULT_NEGATIONS_LIMIT); |
| int count = v.getNegationsCount(); |
| if (count > limit) { |
| return ErrorDescriptionFactory.forName(ctx, t, |
| TEXT_MethodMultipleNegations(method.getName().toString(), count) |
| ); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Counts number of 'negations' in a Tree. A negation is either unary ! or binary != inequality |
| * operator. |
| */ |
| private static class NegationsVisitor extends ErrorAwareTreePathScanner { |
| private int negationsCount; |
| private final boolean ignoreAsserts; |
| |
| public NegationsVisitor(boolean ignoreAsserts) { |
| this.ignoreAsserts = ignoreAsserts; |
| } |
| |
| public int getNegationsCount() { |
| return negationsCount; |
| } |
| |
| @Override |
| public Object visitUnary(UnaryTree node, Object p) { |
| if (node.getKind() == Tree.Kind.LOGICAL_COMPLEMENT) { |
| negationsCount++; |
| } |
| return super.visitUnary(node, p); |
| } |
| |
| @Override |
| public Object visitBinary(BinaryTree node, Object p) { |
| if (node.getKind() == Tree.Kind.NOT_EQUAL_TO) { |
| negationsCount++; |
| } |
| return super.visitBinary(node, p); |
| } |
| |
| @Override |
| public Object visitAssert(AssertTree node, Object p) { |
| int saveCount = negationsCount; |
| Object o = super.visitAssert(node, p); |
| if (ignoreAsserts) { |
| this.negationsCount = saveCount; |
| } |
| return o; |
| } |
| } |
| |
| /** |
| * Utility scanner class, which counts all kinds of loops in the scanned subtree |
| */ |
| private static class LoopFinder extends ErrorAwareTreePathScanner { |
| private int loopCount; |
| |
| public int getLoopCount() { |
| return loopCount; |
| } |
| |
| @Override |
| public Object visitClass(ClassTree node, Object p) { |
| int save = loopCount; |
| Object o = super.visitClass(node, p); |
| this.loopCount = save; |
| return o; |
| } |
| |
| @Override |
| public Object visitDoWhileLoop(DoWhileLoopTree node, Object p) { |
| loopCount++; |
| return super.visitDoWhileLoop(node, p); |
| } |
| |
| @Override |
| public Object visitWhileLoop(WhileLoopTree node, Object p) { |
| loopCount++; |
| return super.visitWhileLoop(node, p); |
| } |
| |
| @Override |
| public Object visitForLoop(ForLoopTree node, Object p) { |
| loopCount++; |
| return super.visitForLoop(node, p); |
| } |
| |
| @Override |
| public Object visitEnhancedForLoop(EnhancedForLoopTree node, Object p) { |
| loopCount++; |
| return super.visitEnhancedForLoop(node, p); |
| } |
| |
| |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_MethodMultipleLoops", |
| description = "#DESC_MethodMultipleLoops", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @TriggerTreeKind(Tree.Kind.METHOD) |
| @UseOptions({ OPTION_LOOPS_LIMIT }) |
| public static ErrorDescription multipleLoops(HintContext ctx) { |
| Tree t = ctx.getPath().getLeaf(); |
| MethodTree method = (MethodTree)t; |
| |
| LoopFinder v = new LoopFinder(); |
| v.scan(ctx.getPath(), null); |
| int count = v.getLoopCount(); |
| int limit = ctx.getPreferences().getInt(OPTION_LOOPS_LIMIT, DEFAULT_LOOPS_LIMIT); |
| if (count > limit) { |
| return ErrorDescriptionFactory.forName(ctx, t, |
| TEXT_MethodMultipleLoops(method.getName().toString(), count) |
| ); |
| } else { |
| return null; |
| } |
| } |
| |
| @Hint( |
| category = "metrics", |
| displayName = "#DN_MethodCoupled", |
| description = "#DESC_MethodCoupled", |
| options = { Hint.Options.QUERY, Hint.Options.HEAVY }, |
| enabled = false |
| ) |
| @TriggerTreeKind(Tree.Kind.METHOD) |
| @UseOptions({ OPTION_COUPLING_LIMIT, OPTION_COUPLING_IGNORE_JAVA }) |
| public static ErrorDescription tooManyDependencies(HintContext ctx) { |
| MethodTree m = (MethodTree)ctx.getPath().getLeaf(); |
| boolean ignoreJava = ctx.getPreferences().getBoolean(OPTION_COUPLING_IGNORE_JAVA, DEFAULT_COUPLING_IGNORE_JAVA); |
| TypeElement outermost = ctx.getInfo().getElementUtilities().outermostTypeElement(ctx.getInfo().getTrees().getElement(ctx.getPath())); |
| |
| DependencyCollector col = new DependencyCollector(ctx.getInfo()); |
| col.setIgnoreJavaLibraries(ignoreJava); |
| col.setOutermostClass(outermost); |
| |
| /* |
| left for the case that superclass references should be excluded optionally |
| ExecutableElement el = (ExecutableElement)ctx.getInfo().getTrees().getElement(ctx.getPath()); |
| Element parent = el.getEnclosingElement(); |
| while (parent != null && |
| (parent.getKind() == ElementKind.INTERFACE || parent.getKind() == ElementKind.CLASS || parent.getKind() == ElementKind.ENUM)) { |
| |
| Element p = parent; |
| while (true) { |
| TypeElement tel = (TypeElement)p; |
| col.addIgnoredQName(tel.getQualifiedName()); |
| TypeMirror tm = tel.getSuperclass(); |
| if (tm.getKind() == TypeKind.DECLARED) { |
| p = ctx.getInfo().getTypes().asElement(tm); |
| } else { |
| break; |
| } |
| } |
| parent = parent.getEnclosingElement(); |
| }*/ |
| |
| col.scan(ctx.getPath(), null); |
| |
| int deps = col.getSeenQNames().size(); |
| int limit = ctx.getPreferences().getInt(OPTION_COUPLING_LIMIT, DEFAULT_COUPLING_LIMIT); |
| if (deps > limit) { |
| return ErrorDescriptionFactory.forName(ctx, m, TEXT_MethodTooCoupled(m.getName().toString(), deps)); |
| } else { |
| return null; |
| } |
| } |
| } |