Skip to content

Commit

Permalink
feat: add inner_class_may_be_static rule (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinWitt authored Oct 28, 2022
1 parent 6dc849e commit 7fa964f
Show file tree
Hide file tree
Showing 8 changed files with 775 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.function.Function;
import xyz.keksdose.spoon.code_solver.analyzer.AnalyzerRule;
import xyz.keksdose.spoon.code_solver.analyzer.qodana.rules.AbstractRefactoring;
import xyz.keksdose.spoon.code_solver.analyzer.qodana.rules.InnerClassMayBeStatic;
import xyz.keksdose.spoon.code_solver.analyzer.qodana.rules.MethodMayBeStatic;
import xyz.keksdose.spoon.code_solver.analyzer.qodana.rules.NonProtectedConstructorInAbstractClass;
import xyz.keksdose.spoon.code_solver.analyzer.qodana.rules.NonStrictComparisonCanBeEquality;
Expand Down Expand Up @@ -35,7 +36,8 @@ public enum QodanaRules implements AnalyzerRule {
UNUSED_IMPORT("UnusedImport", UnusedImport::new),
PROTECTED_MEMBER_IN_FINAL_CLASS("ProtectedMemberInFinalClass", ProtectedMemberInFinalClass::new),
UNNECESSARY_MODIFIER("UnnecessaryModifier", UnnecessaryModifier::new),
POINTLESS_BOOLEAN_EXPRESSION("PointlessBooleanExpression", PointlessBooleanExpression::new);
POINTLESS_BOOLEAN_EXPRESSION("PointlessBooleanExpression", PointlessBooleanExpression::new),
INNER_CLASS_MAY_BE_STATIC("InnerClassMayBeStatic", InnerClassMayBeStatic::new);

private final String ruleId;
private final Function<AnalyzerResult, AbstractRefactoring> refactoring;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package xyz.keksdose.spoon.code_solver.analyzer.qodana.rules;

import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.ModifierKind;
import xyz.keksdose.spoon.code_solver.analyzer.PositionScanner;
import xyz.keksdose.spoon.code_solver.api.analyzer.AnalyzerResult;
import xyz.keksdose.spoon.code_solver.history.Change;
import xyz.keksdose.spoon.code_solver.history.ChangeListener;
import xyz.keksdose.spoon.code_solver.history.MarkdownString;
import xyz.keksdose.spoon.code_solver.transformations.BadSmell;

public class InnerClassMayBeStatic extends AbstractRefactoring {

private static final BadSmell badSmell = new BadSmell() {
@Override
public MarkdownString getName() {
return MarkdownString.fromMarkdown("InnerClassMayBeStatic");
}

@Override
public MarkdownString getDescription() {
return MarkdownString.fromMarkdown(
"""
Inner classes that do not reference their enclosing instances can be made static.
This prevents a common cause of memory leaks and uses less memory per instance of the class.
""");
}
};

public InnerClassMayBeStatic(AnalyzerResult result) {
super(result);
}

@Override
public void refactor(ChangeListener listener, CtType<?> type) {
if (!isSameType(type, Path.of(result.filePath()))) {
return;
}
for (CtType<?> ctType : filterMatches(PositionScanner.findLineOnly(type, result.position()))) {
var modifiers = new HashSet<>(ctType.getModifiers());
modifiers.add(ModifierKind.STATIC);
ctType.setModifiers(modifiers);
String format = "Add to inner class %s modifier static";
String markdown = String.format(format, "`" + ctType.getSimpleName() + "`");
String text = String.format(format, ctType.getQualifiedName());
MarkdownString markdownString = MarkdownString.fromMarkdown(text, markdown);
Change change = new Change(badSmell, markdownString, getMostOuterType(type), result);
listener.setChanged(getMostOuterType(type), change);
}
}

private Iterable<? extends CtType<?>> filterMatches(List<CtElement> findLineOnly) {
return findLineOnly.stream()
.filter(v -> v instanceof CtType<?>)
.map(v -> (CtType<?>) v)
.filter(v -> !v.isTopLevel())
.filter(v -> !v.getModifiers().contains(ModifierKind.STATIC))
.toList();
}

@Override
public List<BadSmell> getHandledBadSmells() {
return List.of(badSmell);
}

private CtType<?> getMostOuterType(CtType<?> inner) {
if (inner.getDeclaringType() == null) {
return inner;
} else {
return getMostOuterType(inner.getDeclaringType());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ public class Change {
public Change(String text, String issue, CtType<?> affectedType) {
this.text = MarkdownString.fromRaw(text);
this.issue = issue;
this.affectedType = affectedType;
this.affectedType = getMostOuterType(affectedType);
}

public Change(BadSmell badSmell, MarkdownString text, CtType<?> affectedType) {
this.text = text;
this.issue = badSmell.getName().asText();
this.badsmell = badSmell;
this.affectedType = affectedType;
this.affectedType = getMostOuterType(affectedType);
}

public Change(BadSmell badSmell, MarkdownString text, CtType<?> affectedType, AnalyzerResult analyzerResult) {
Expand Down Expand Up @@ -85,4 +85,12 @@ public boolean equals(Object obj) {
public @Nullable AnalyzerResult getAnalyzerResult() {
return analyzerResult;
}

private CtType<?> getMostOuterType(CtType<?> inner) {
if (inner.getDeclaringType() == null) {
return inner;
} else {
return getMostOuterType(inner.getDeclaringType());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package xyz.keksdose.spoon.code_solver.transformations.qodana;

import java.io.File;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import xyz.keksdose.spoon.code_solver.analyzer.qodana.rules.InnerClassMayBeStatic;
import xyz.keksdose.spoon.code_solver.api.analyzer.AnalyzerResult;
import xyz.keksdose.spoon.code_solver.api.analyzer.Position;
import xyz.keksdose.spoon.code_solver.transformations.TestAnalyzerResult;
import xyz.keksdose.spoon.code_solver.transformations.TransformationTestUtils;

public class InnerClassMayBeStaticTest {

@Test
void landLordInnerClassMabyBeStatic(@TempDir File dir) throws IOException {
Position position = new Position(296, 0, 0, 0, 0, 80);
AnalyzerResult result = new TestAnalyzerResult("Landlordbase.java", position);
String resourcePath = "projects/refactorings/InnerClassMayBeStatic/Landlordbase.java";
var copy = TransformationTestUtils.transform(new InnerClassMayBeStatic(result), resourcePath, dir);
TransformationTestUtils.compareContent(copy, resourcePath);
}
}
Loading

0 comments on commit 7fa964f

Please sign in to comment.