Skip to content

Commit

Permalink
#33 "source code style rule definition"
Browse files Browse the repository at this point in the history
- just some numbers that felt okay enough
  • Loading branch information
janScheible committed May 28, 2022
1 parent 5431635 commit e7ecf3b
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 172 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,4 @@ Unresolvable methods (no coverage information available):
1. utility classes must be `abstract` and have a private default constructor
1. exception: real constants with names in upper case delimited by underscores
1. `logger` has to be protected final but not static: `protected final Logger logger = LoggerFactory.getLogger(getClass());`
1. restrict file, method and lambda lengths to reasonable values
12 changes: 12 additions & 0 deletions test-gap-analysis/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,17 @@
<module name="ConstantName">
<property name="format" value="^(logger|[A-Z][A-Z0-9]*(_[A-Z0-9]+)*)$"/>
</module>

<module name="LambdaBodyLength">
<property name="max" value="5"/>
</module>
<module name="MethodLength">
<property name="max" value="40"/>
<property name="countEmpty" value="false"/>
</module>
</module>

<module name="FileLength">
<property name="max" value="300"/>
</module>
</module>
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.scheible.testgapanalysis.common.FilesUtils;
Expand Down Expand Up @@ -57,20 +56,15 @@ public DebugCoverageResolutionReport run(final File workDir, final File sourceDi
}

private ParseResult parseMethods(final File workingDir) throws UncheckedIOException {
final AtomicInteger javaFileCount = new AtomicInteger(0);
final Set<ParsedMethod> methods = new HashSet<>();

try (Stream<Path> walkStream = Files.walk(workingDir.toPath())) {
walkStream.filter(p -> p.toFile().isFile()).forEach(file -> {
if (file.toString().endsWith(".java")) {
javaFileCount.incrementAndGet();
methods.addAll(javaParser.getMethods(FilesUtils.readUtf8(file.toFile())));
}
});
final Set<File> javaFiles = walkStream.filter(p -> p.toFile().isFile() && p.toString().endsWith(".java"))
.map(Path::toFile).collect(Collectors.toSet());
final Set<ParsedMethod> methods = javaFiles.stream()
.flatMap(f -> javaParser.getMethods(FilesUtils.readUtf8(f)).stream()).collect(Collectors.toSet());

return new ParseResult(methods, javaFiles.size());
} catch (IOException ex) {
throw new UncheckedIOException("Error while reading Java sources.", ex);
}

return new ParseResult(methods, javaFileCount.get());
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,12 @@
package com.scheible.testgapanalysis.parser;

import static java.lang.Boolean.FALSE;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import com.github.javaparser.ParseProblemException;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ParserConfiguration.LanguageLevel;
import com.github.javaparser.Range;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.body.InitializerDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.comments.BlockComment;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.ast.comments.LineComment;
import com.github.javaparser.ast.expr.LambdaExpr;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.scheible.testgapanalysis.parser.ParsedMethod.MethodType;

/**
*
Expand All @@ -37,10 +15,6 @@
public class JavaParser {

public Set<ParsedMethod> getMethods(final String code) {
final Set<ParsedMethod> result = new HashSet<>();

final AtomicBoolean debugMode = new AtomicBoolean(false);

final ParserConfiguration configuration = new ParserConfiguration();
configuration.setLanguageLevel(LanguageLevel.BLEEDING_EDGE);
final com.github.javaparser.JavaParser javaParser = new com.github.javaparser.JavaParser(configuration);
Expand All @@ -50,138 +24,8 @@ public Set<ParsedMethod> getMethods(final String code) {
throw new ParseProblemException(parserResult.getProblems());
}

parserResult.getResult().get().accept(new VoidVisitorAdapter<Void>() {
@Override
public void visit(final ClassOrInterfaceDeclaration node, final Void arg) {
// if class is marked with '//#debug' enable debug mode
if (node.getName().getComment().map(Comment::getContent).orElse("").contains("#debug")) {
debugMode.set(true);
}

super.visit(node, arg);
}

@Override
public void visit(final ConstructorDeclaration node, final Void arg) {
if (node.getRange().isPresent()) {
final Range range = node.getRange().get();
final String relevantCode = MaskUtils.apply(code, range, findMasks(node), debugMode.get());
final List<String> argumentTypes = node.getParameters().stream().map(Parameter::getType)
.map(t -> t.asString() + (((Parameter) t.getParentNode().get()).isVarArgs() ? "[]" : ""))
.collect(Collectors.toList());

final boolean enumConstructor = node.getParentNode().filter(pn -> pn instanceof EnumDeclaration)
.isPresent();
final boolean innerClassConstructor = node.getParentNode()
.filter(pn -> pn instanceof ClassOrInterfaceDeclaration)
.map(pn -> (ClassOrInterfaceDeclaration) pn).map(pn -> pn.isInnerClass() && !pn.isStatic())
.orElse(FALSE);

final MethodType type = enumConstructor
? MethodType.ENUM_CONSTRUCTOR
: innerClassConstructor ? MethodType.INNER_CLASS_CONSTRUCTOR : MethodType.CONSTRUCTOR;

result.add(new ParsedMethod(type, ParserUtils.getTopLevelFqn(node), ParserUtils.getScope(node),
"<init>", relevantCode, ParserUtils.getCodeLines(node), range.begin.column,
!ParserUtils.containsCode(node.getBody()), argumentTypes,
ParserUtils.getTypeParameters(node), ParserUtils.getOuterDeclaringType(node)));
}

super.visit(node, arg);
}

@Override
public void visit(final InitializerDeclaration node, final Void arg) {
if (node.getRange().isPresent()) {
final Range range = node.getRange().get();
final String relevantCode = MaskUtils.apply(code, range, findMasks(node), debugMode.get());

result.add(new ParsedMethod(
node.isStatic() ? MethodType.STATIC_INITIALIZER : MethodType.INITIALIZER,
ParserUtils.getTopLevelFqn(node), ParserUtils.getScope(node),
node.isStatic() ? "<clinit>" : "<initbl>", relevantCode, ParserUtils.getCodeLines(node),
range.begin.column, !ParserUtils.containsCode(node.getBody()), 0));
}

super.visit(node, arg);
}

@Override
public void visit(final MethodDeclaration node, final Void arg) {
if (node.getRange().isPresent() && node.getBody().isPresent()) {
final Range range = node.getRange().get();
final String relevantCode = MaskUtils.apply(code, range, findMasks(node), debugMode.get());

result.add(new ParsedMethod(node.isStatic() ? MethodType.STATIC_METHOD : MethodType.METHOD,
ParserUtils.getTopLevelFqn(node), ParserUtils.getScope(node), node.getNameAsString(),
relevantCode, ParserUtils.getCodeLines(node), range.begin.column,
!ParserUtils.containsCode(node.getBody().get()), node.getParameters().size()));
}

super.visit(node, arg);
}

@Override
public void visit(final LambdaExpr node, final Void arg) {
if (node.getRange().isPresent()) {
final Range range = node.getRange().get();
final String relevantCode = MaskUtils.apply(code, range, findMasks(node), debugMode.get());

result.add(new ParsedMethod(MethodType.LAMBDA_METHOD, ParserUtils.getTopLevelFqn(node),
ParserUtils.getScope(node), "lambda", relevantCode, ParserUtils.getCodeLines(node),
range.begin.column, !ParserUtils.containsCode(node.getBody()),
node.getParameters().size()));
}

super.visit(node, arg);
}
}, null);

return result;
}

private static boolean isMethod(final Node node) {
return node instanceof ConstructorDeclaration || node instanceof InitializerDeclaration
|| node instanceof MethodDeclaration || node instanceof LambdaExpr;
}

private static boolean isComment(final Node node) {
return node instanceof LineComment || node instanceof BlockComment;
}

/**
* Identifies all parts of a method (constructor, (static) initializer, (lambda) method) that shouldn't be
* treated as part of its own code (could be either other nested methods or comments).
*/
private static List<Range> findMasks(final Node node) {
if (isMethod(node)) {
final List<Range> masks = new ArrayList<>();
findMasks(node, masks);
return masks;
} else {
throw new IllegalArgumentException("Only methods are allowed!");
}
}

private static void findMasks(final Node node, final List<Range> masks) {
addCommentRangeIfAny(node, masks);

for (final Node child : node.getChildNodes()) {
if (isMethod(child) || isComment(child)) {
if (child.getRange().isPresent()) {
masks.add(child.getRange().get());
}

addCommentRangeIfAny(child, masks);
} else {
findMasks(child, masks);
}
}
}

private static void addCommentRangeIfAny(final Node node, final Collection<Range> masks) {
if (node.getComment().isPresent() && node.getComment().get().getRange().isPresent()) {
masks.add(node.getComment().get().getRange().get());
}
final MethodVisitor methodVisitor = new MethodVisitor(code);
parserResult.getResult().get().accept(methodVisitor, null);
return methodVisitor.getResult();
}
}
Loading

0 comments on commit e7ecf3b

Please sign in to comment.