Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use first line number from LineNumberTable for JavaCodeUnit's sourceCodeLocation #344

Merged
merged 1 commit into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -911,8 +911,8 @@ Stream<DynamicTest> MethodsTest() {

.ofRule("no code units that are declared in classes that reside in a package '..persistence..' "
+ "should be annotated with @" + Secured.class.getSimpleName())
.by(ExpectedConstructor.of(SomeJpa.class).beingAnnotatedWith(Secured.class))
.by(ExpectedMethod.of(OtherJpa.class, "getEntityManager").beingAnnotatedWith(Secured.class))
.by(ExpectedConstructor.of(SomeJpa.class).inLine(15).beingAnnotatedWith(Secured.class))
.by(ExpectedMethod.of(OtherJpa.class, "getEntityManager").inLine(31).beingAnnotatedWith(Secured.class))

.toDynamicTests();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,23 @@ public static ExpectedConstructor.Creator of(Class<?> owner, Class<?>... params)
public static class Creator {
private final Class<?> clazz;
private final Class<?>[] params;
private int lineNumber;

private Creator(Class<?> clazz, Class<?>[] params) {
this.clazz = clazz;
this.params = params;
}

public Creator inLine(int lineNumber) {
this.lineNumber = lineNumber;
return this;
}

public ExpectedMessage beingAnnotatedWith(Class<? extends Annotation> annotationType) {
return new ExpectedMessage(String.format("Constructor <%s> is annotated with @%s in (%s.java:0)",
return new ExpectedMessage(String.format("Constructor <%s> is annotated with @%s in (%s.java:%d)",
formatMethod(clazz.getName(), JavaConstructor.CONSTRUCTOR_NAME, JavaClass.namesOf(params)),
annotationType.getSimpleName(),
clazz.getSimpleName()));
clazz.getSimpleName(), lineNumber));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ public static class Creator {
private final Class<?> clazz;
private final String methodName;
private final Class<?>[] params;
private int lineNumber;

private Creator(Class<?> clazz, String methodName, Class<?>[] params) {
this.clazz = clazz;
this.methodName = methodName;
this.params = params;
}

public Creator inLine(int lineNumber) {
this.lineNumber = lineNumber;
return this;
}

public ExpectedMessage toNotHaveRawReturnType(Class<?> type) {
return method("does not have raw return type " + type.getName());
}
Expand All @@ -37,7 +43,7 @@ public ExpectedMessage beingAnnotatedWith(Class<? extends Annotation> annotation

private ExpectedMessage method(String message) {
String methodDescription = format("Method <%s>", formatMethod(clazz.getName(), methodName, JavaClass.namesOf(params)));
String sourceCodeLocation = format("(%s.java:0)", clazz.getSimpleName());
String sourceCodeLocation = format("(%s.java:%d)", clazz.getSimpleName(), lineNumber);
return new ExpectedMessage(format("%s %s in %s", methodDescription, message, sourceCodeLocation));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.tngtech.archunit.Internal;
import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.base.HasDescription;
import com.tngtech.archunit.base.Optional;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
import com.tngtech.archunit.core.domain.properties.HasAnnotations;
Expand Down Expand Up @@ -59,7 +58,7 @@ public abstract class JavaMember implements
this.descriptor = checkNotNull(builder.getDescriptor());
this.annotations = builder.getAnnotations(this);
this.owner = checkNotNull(builder.getOwner());
this.sourceCodeLocation = SourceCodeLocation.of(owner);
this.sourceCodeLocation = SourceCodeLocation.of(owner, builder.getFirstLineNumber());
this.modifiers = checkNotNull(builder.getModifiers());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public abstract static class JavaMemberBuilder<OUTPUT, SELF extends JavaMemberBu
private Set<JavaModifier> modifiers;
private JavaClass owner;
private ClassesByTypeName importedClasses;
private int firstLineNumber;

private JavaMemberBuilder() {
}
Expand All @@ -142,6 +143,10 @@ SELF withModifiers(Set<JavaModifier> modifiers) {
return self();
}

void recordLineNumber(int lineNumber) {
this.firstLineNumber = this.firstLineNumber == 0 ? lineNumber : Math.min(this.firstLineNumber, lineNumber);
}

@SuppressWarnings("unchecked")
SELF self() {
return (SELF) this;
Expand Down Expand Up @@ -178,6 +183,10 @@ public JavaClass getOwner() {
return owner;
}

public int getFirstLineNumber() {
return firstLineNumber;
}

@Override
public final OUTPUT build(JavaClass owner, ClassesByTypeName importedClasses) {
this.owner = owner;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ public void visitCode() {
@Override
public void visitLineNumber(int line, Label start) {
LOG.trace("Examining line number {}", line);
codeUnitBuilder.recordLineNumber(line);
actualLineNumber = line;
accessHandler.setLineNumber(actualLineNumber);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
import com.tngtech.archunit.core.importer.testexamples.integration.ClassD;
import com.tngtech.archunit.core.importer.testexamples.integration.ClassXDependingOnClassesABCD;
import com.tngtech.archunit.core.importer.testexamples.integration.InterfaceOfClassX;
import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithMultipleMethods;
import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithObjectVoidAndIntIntSerializableMethod;
import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithStringStringMethod;
import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithThrowingMethod;
Expand Down Expand Up @@ -739,6 +740,36 @@ public void imports_methods_with_correct_throws_declarations() throws Exception
assertThat(method.getExceptionTypes()).matches(FirstCheckedException.class, SecondCheckedException.class);
}

@Test
public void imports_members_with_sourceCodeLocation() throws Exception {
ImportedClasses importedClasses = classesIn("testexamples/methodimport");
String sourceFileName = "ClassWithMultipleMethods.java";

JavaClass javaClass = importedClasses.get(ClassWithMultipleMethods.class);
assertThat(javaClass.getField("usage").getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":0)"); // the byte code has no line number associated with a field
assertThat(javaClass.getConstructor().getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":3)"); // auto-generated constructor seems to get line of class definition
assertThat(javaClass.getStaticInitializer().get().getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":5)"); // auto-generated static initializer seems to get line of first static variable definition
assertThat(javaClass.getMethod("methodDefinedInLine7").getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":7)");
assertThat(javaClass.getMethod("methodWithBodyStartingInLine10").getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":10)");
assertThat(javaClass.getMethod("emptyMethodDefinedInLine15").getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":15)");
assertThat(javaClass.getMethod("emptyMethodEndingInLine19").getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":19)");

javaClass = importedClasses.get(ClassWithMultipleMethods.InnerClass.class);
assertThat(javaClass.getMethod("methodWithBodyStartingInLine24").getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":24)");

javaClass = importedClasses.get(ClassWithMultipleMethods.InnerClass.class.getName() + "$1");
assertThat(javaClass.getMethod("run").getSourceCodeLocation())
.hasToString("(" + sourceFileName + ":27)");
}

@Test
public void imports_methods_with_one_annotation_correctly() throws Exception {
JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.tngtech.archunit.core.importer.testexamples.methodimport;

public class ClassWithMultipleMethods {

static String usage = "ClassFileImporterTest's @Test imports_methods_with_correct_sourceCodeLocation";

int methodDefinedInLine7() { return 7; }

void methodWithBodyStartingInLine10() {
System.out.println(10);
System.out.println(11);
System.out.println(12);
}

void emptyMethodDefinedInLine15() { }

void emptyMethodEndingInLine19() {

}

public static class InnerClass {

void methodWithBodyStartingInLine24() {
new Runnable() {
@Override
public void run() {
System.out.println(27);
}
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,8 @@ public void classes_should_have_only_private_constructor(ArchRule rule) {
assertThat(rule).checking(importClasses(ClassWithPrivateConstructors.class))
.hasNoViolation();
assertThat(rule).checking(importClasses(ClassWithPublicAndPrivateConstructor.class))
.hasOnlyViolations(String.format("Constructor <%s.<init>(%s)> is not private in (%s.java:0)",
ClassWithPublicAndPrivateConstructor.class.getName(), String.class.getName(), getClass().getSimpleName()));
.hasOnlyViolations(String.format("Constructor <%s.<init>(%s)> is not private in (%s.java:%d)",
ClassWithPublicAndPrivateConstructor.class.getName(), String.class.getName(), getClass().getSimpleName(), 1538));
}

@DataProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ public void orShould_ORs_conditions(ArchRule rule) {
RightOne.class.getName(), RightTwo.class.getName()));
assertThat(report.getDetails()).containsOnly(
String.format("%s and %s",
isNotDeclaredInMessage(WrongOne.class, CONSTRUCTOR_NAME, RightOne.class),
isNotDeclaredInMessage(WrongOne.class, CONSTRUCTOR_NAME, RightTwo.class)),
isNotDeclaredInMessage(WrongOne.class, CONSTRUCTOR_NAME, RightOne.class, 111),
isNotDeclaredInMessage(WrongOne.class, CONSTRUCTOR_NAME, RightTwo.class, 111)),
String.format("%s and %s",
isNotDeclaredInMessage(WrongOne.class, "wrongMethod1", RightOne.class),
isNotDeclaredInMessage(WrongOne.class, "wrongMethod1", RightTwo.class)));
isNotDeclaredInMessage(WrongOne.class, "wrongMethod1", RightOne.class, 113),
isNotDeclaredInMessage(WrongOne.class, "wrongMethod1", RightTwo.class, 113)));
}

@DataProvider
Expand All @@ -75,24 +75,24 @@ public void andShould_ANDs_conditions(ArchRule rule) {
"members should not be declared in %s and should not be declared in %s",
WrongOne.class.getName(), WrongTwo.class.getName()));
assertThat(report.getDetails()).containsOnly(
isDeclaredInMessage(WrongOne.class, CONSTRUCTOR_NAME),
isDeclaredInMessage(WrongOne.class, "wrongMethod1"),
isDeclaredInMessage(WrongTwo.class, CONSTRUCTOR_NAME),
isDeclaredInMessage(WrongTwo.class, "wrongMethod2"));
isDeclaredInMessage(WrongOne.class, CONSTRUCTOR_NAME, 111),
isDeclaredInMessage(WrongOne.class, "wrongMethod1", 113),
isDeclaredInMessage(WrongTwo.class, CONSTRUCTOR_NAME, 117),
isDeclaredInMessage(WrongTwo.class, "wrongMethod2", 119));
}

private String isDeclaredInMessage(Class<?> clazz, String methodName) {
return message(clazz, methodName, "", clazz);
private String isDeclaredInMessage(Class<?> clazz, String methodName, int lineNumber) {
return message(clazz, methodName, "", clazz, lineNumber);
}

private String isNotDeclaredInMessage(Class<?> clazz, String methodName, Class<?> expectedTarget) {
return message(clazz, methodName, "not ", expectedTarget);
private String isNotDeclaredInMessage(Class<?> clazz, String methodName, Class<?> expectedTarget, int lineNumber) {
return message(clazz, methodName, "not ", expectedTarget, lineNumber);
}

private String message(Class<?> clazz, String methodName, String optionalNot, Class<?> expectedTarget) {
return String.format("%s <%s.%s()> is %sdeclared in %s in (%s.java:0)",
private String message(Class<?> clazz, String methodName, String optionalNot, Class<?> expectedTarget, int lineNumber) {
return String.format("%s <%s.%s()> is %sdeclared in %s in (%s.java:%d)",
CONSTRUCTOR_NAME.equals(methodName) ? "Constructor" : "Method",
clazz.getName(), methodName, optionalNot, expectedTarget.getName(), getClass().getSimpleName());
clazz.getName(), methodName, optionalNot, expectedTarget.getName(), getClass().getSimpleName(), lineNumber);
}

@SuppressWarnings("unused")
Expand Down