Skip to content

Commit 1fc4ead

Browse files
committed
chore: ObjectCreationExpr out of scope; Closes #41.
1 parent 8a87e0e commit 1fc4ead

7 files changed

Lines changed: 132 additions & 99 deletions

File tree

assis-core/src/main/java/io/github/masmangan/assis/internal/ClassDiagramGeneration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* <p>
3030
* The output file is written using UTF-8.
3131
*
32+
* @author Marco Mangan
3233
*/
3334
public final class ClassDiagramGeneration {
3435

@@ -73,6 +74,7 @@ public void run() throws IOException {
7374
writeStructuralRelations(pw, er);
7475

7576
writeDependencies(pw, er);
77+
7678
pw.println();
7779

7880
pw.endDiagram("class-diagram");

assis-core/src/main/java/io/github/masmangan/assis/internal/CollectDependenciesVisitor.java

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.github.javaparser.ast.expr.InstanceOfExpr;
2424
import com.github.javaparser.ast.expr.MethodCallExpr;
2525
import com.github.javaparser.ast.expr.NameExpr;
26-
import com.github.javaparser.ast.expr.ObjectCreationExpr;
2726
import com.github.javaparser.ast.type.ClassOrInterfaceType;
2827
import com.github.javaparser.ast.type.Type;
2928
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
@@ -47,6 +46,26 @@ public void visit(ClassOrInterfaceDeclaration n, DependencyContext ctx) {
4746
exit();
4847
}
4948

49+
// no samples!
50+
@Override
51+
public void visit(EnumDeclaration n, DependencyContext ctx) {
52+
enter(n);
53+
super.visit(n, ctx);
54+
exit();
55+
}
56+
57+
@Override
58+
public void visit(RecordDeclaration n, DependencyContext ctx) {
59+
enter(n);
60+
61+
// Record components are parameters, not fields.
62+
n.getParameters().forEach(p -> recordTypeUse(p.getType(), p, ctx));
63+
64+
super.visit(n, ctx);
65+
exit();
66+
}
67+
68+
// no samples!
5069
@Override
5170
public void visit(FieldDeclaration fd, DependencyContext ctx) {
5271
if (ownerStack.isEmpty()) {
@@ -62,30 +81,36 @@ public void visit(FieldDeclaration fd, DependencyContext ctx) {
6281
}
6382

6483
@Override
65-
public void visit(ObjectCreationExpr n, DependencyContext ctx) {
66-
logger.log(Level.INFO, () -> "Object creationg for " + n);
67-
recordTypeUse(n.getType(), n, ctx);
68-
super.visit(n, ctx);
69-
}
84+
public void visit(MethodDeclaration md, DependencyContext ctx) {
85+
logger.log(Level.INFO, () -> "Collecting dependencies for " + md);
7086

71-
@Override
72-
public void visit(EnumDeclaration n, DependencyContext ctx) {
73-
enter(n);
74-
super.visit(n, ctx);
75-
exit();
76-
}
87+
if (ownerStack.isEmpty()) {
88+
super.visit(md, ctx);
89+
return;
90+
}
7791

78-
@Override
79-
public void visit(RecordDeclaration n, DependencyContext ctx) {
80-
enter(n);
92+
// 1) Parameters: m(b : B) -> A ..> B
93+
md.getParameters().forEach(p -> recordTypeUse(p.getType(), p, ctx));
8194

82-
// Record components are parameters, not fields.
83-
n.getParameters().forEach(p -> recordTypeUse(p.getType(), p, ctx));
95+
// 2) Throws clause: m() throws X -> A ..> X
96+
md.getThrownExceptions().forEach(t -> recordTypeUse(t, t, ctx));
8497

85-
super.visit(n, ctx);
86-
exit();
98+
// 3) Return type (keep consistent with other type uses)
99+
if (!md.getType().isVoidType()) {
100+
recordTypeUse(md.getType(), md, ctx);
101+
}
102+
103+
super.visit(md, ctx);
87104
}
88105

106+
// related to visitor bug
107+
// @Override
108+
// public void visit(ObjectCreationExpr n, DependencyContext ctx) {
109+
// logger.log(Level.INFO, () -> "Object creation for " + n);
110+
// recordTypeUse(n.getType(), n, ctx);
111+
// super.visit(n, ctx);
112+
// }
113+
89114
@Override
90115
public void visit(InstanceOfExpr n, DependencyContext ctx) {
91116
recordTypeUse(n.getType(), n, ctx);
@@ -104,6 +129,7 @@ public void visit(ClassExpr n, DependencyContext ctx) {
104129
super.visit(n, ctx);
105130
}
106131

132+
// no samples!
107133
@Override
108134
public void visit(MethodCallExpr n, DependencyContext ctx) {
109135
n.getScope().ifPresent(scope -> {
@@ -114,27 +140,16 @@ public void visit(MethodCallExpr n, DependencyContext ctx) {
114140
super.visit(n, ctx);
115141
}
116142

117-
@Override
118-
public void visit(MethodDeclaration md, DependencyContext ctx) {
119-
logger.log(Level.INFO, () -> "Collecting dependencies for " + md);
120-
121-
if (ownerStack.isEmpty()) {
122-
super.visit(md, ctx);
123-
return;
124-
}
125-
126-
// 1) Parameters: m(b : B) -> A ..> B
127-
md.getParameters().forEach(p -> recordTypeUse(p.getType(), p, ctx));
128-
129-
// 2) Throws clause: m() throws X -> A ..> X
130-
md.getThrownExceptions().forEach(t -> recordTypeUse(t, t, ctx));
143+
private void enter(TypeDeclaration<?> td) {
144+
ownerStack.push(td);
145+
}
131146

132-
// 3) Return type (keep consistent with other type uses)
133-
if (!md.getType().isVoidType()) {
134-
recordTypeUse(md.getType(), md, ctx);
135-
}
147+
private void exit() {
148+
ownerStack.pop();
149+
}
136150

137-
super.visit(md, ctx);
151+
private TypeDeclaration<?> owner() {
152+
return ownerStack.peek();
138153
}
139154

140155
private void recordTypeUse(Type typeNode, Node site, DependencyContext ctx) {
@@ -143,26 +158,13 @@ private void recordTypeUse(Type typeNode, Node site, DependencyContext ctx) {
143158
return;
144159
}
145160

146-
ctx.resolveTarget(typeNode, site).ifPresent(target -> collect(owner(), target, ctx));
161+
ctx.resolveTarget(typeNode).ifPresent(target -> collect(owner(), target, ctx));
147162

148-
// unwrap generic arguments
149163
if (typeNode instanceof ClassOrInterfaceType cit) {
150164
cit.getTypeArguments().ifPresent(args -> args.forEach(arg -> recordTypeUse(arg, site, ctx)));
151165
}
152166
}
153167

154-
private void enter(TypeDeclaration<?> td) {
155-
ownerStack.push(td);
156-
}
157-
158-
private void exit() {
159-
ownerStack.pop();
160-
}
161-
162-
private TypeDeclaration<?> owner() {
163-
return ownerStack.peek();
164-
}
165-
166168
private void recordScope(String simpleName, Node site, DependencyContext ctx) {
167169
if (ownerStack.isEmpty()) {
168170
return;
@@ -178,7 +180,6 @@ private void collect(TypeDeclaration<?> from, TypeRef to, DependencyContext ctx)
178180
if (to instanceof DeclaredTypeRef) {
179181
ctx.addDependency(from, to);
180182
} else {
181-
// External + Unresolved become ghost deps
182183
ctx.addCherryPick(from, to);
183184
}
184185
}

assis-core/src/main/java/io/github/masmangan/assis/internal/CollectRelationshipsVisitor.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private void emitExtendsImplements(TypeDeclaration<?> td) {
134134
private void emitImplements(TypeDeclaration<?> td, ClassOrInterfaceType impl) {
135135
String subFqn = DeclaredIndex.deriveFqnDollar(td);
136136

137-
Optional<TypeRef> tr = idx.resolveTarget(impl, td);
137+
Optional<TypeRef> tr = idx.resolveTarget(impl);
138138
logger.log(Level.INFO, () -> "Trying to resolve implements type: " + tr);
139139

140140
if (tr.isPresent()) {
@@ -161,7 +161,7 @@ private void emitImplements(TypeDeclaration<?> td, ClassOrInterfaceType impl) {
161161
private void emitExtends(ClassOrInterfaceDeclaration cid, ClassOrInterfaceType ext) {
162162
String subFqn = DeclaredIndex.deriveFqnDollar(cid);
163163

164-
Optional<TypeRef> tr = idx.resolveTarget(ext, cid);
164+
Optional<TypeRef> tr = idx.resolveTarget(ext);
165165
logger.log(Level.INFO, () -> "Trying to resolve extends type: " + tr);
166166

167167
if (tr.isPresent()) {
@@ -241,7 +241,7 @@ private void emitFieldAssociation(String pkg, String ownerFqn, FieldDeclaration
241241
for (VariableDeclarator vd : fd.getVariables()) {
242242
String target = null;
243243

244-
Optional<TypeRef> tr = idx.resolveTarget(vd.getType(), vd); // usageSite can be vd or fd
244+
Optional<TypeRef> tr = idx.resolveTarget(vd.getType()); // usageSite can be vd or fd
245245
logger.log(Level.INFO, () -> "Trying to resolve type: " + tr);
246246

247247
if (tr.isPresent()) {

assis-core/src/main/java/io/github/masmangan/assis/internal/DeclaredIndex.java

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -233,49 +233,70 @@ public Iterable<TypeDeclaration<?>> typesInPackageOrder(String pkg) {
233233
return Collections.unmodifiableList(out);
234234
}
235235

236-
public Optional<TypeRef> resolveTarget(Type typeNode, Node usageSite) {
236+
public Optional<TypeRef> resolveTarget(Type typeNode) {
237237
logger.log(Level.INFO, () -> "Resolving target: " + typeNode);
238238

239-
// If we want arrays like Foo[] to count as dependency on Foo:
239+
if (typeNode == null) {
240+
return Optional.empty();
241+
}
242+
243+
// Arrays: Foo[] depends on Foo
240244
if (typeNode instanceof ArrayType at) {
241-
return resolveTarget(at.getComponentType(), usageSite);
245+
return resolveTarget(at.getComponentType());
246+
}
247+
248+
// Ignore primitives / void as "no dependency"
249+
if (typeNode.isPrimitiveType() || typeNode.isVoidType()) {
250+
return Optional.empty();
242251
}
243252

253+
// Type parameters: T -> first bound if exists, else unresolved "T"
244254
if (typeNode instanceof TypeParameter tp) {
245-
// resolve its first bound if present
246255
if (!tp.getTypeBound().isEmpty()) {
247-
return resolveTarget(tp.getTypeBound().get(0), usageSite);
256+
return resolveTarget(tp.getTypeBound().get(0));
248257
}
249-
return Optional.empty();
258+
return Optional.of(new UnresolvedTypeRef(tp.getNameAsString()));
250259
}
260+
261+
// Wildcards: ? extends Foo -> Foo ; plain ? -> unresolved "?"
251262
if (typeNode instanceof WildcardType wt) {
252-
return wt.getExtendedType().map(t -> resolveTarget(t, usageSite)).orElse(Optional.empty());
253-
}
254-
if (!(typeNode instanceof ClassOrInterfaceType cit)) {
255-
return Optional.empty();
263+
if (wt.getExtendedType().isPresent()) {
264+
return resolveTarget(wt.getExtendedType().get());
265+
}
266+
if (wt.getSuperType().isPresent()) {
267+
return resolveTarget(wt.getSuperType().get());
268+
}
269+
return Optional.of(new UnresolvedTypeRef("?"));
256270
}
257271

258-
logger.log(Level.INFO, () -> "Trying to resolve type: " + cit);
272+
// The "normal" case
273+
if (typeNode instanceof ClassOrInterfaceType cit) {
274+
logger.log(Level.INFO, () -> "Trying to resolve type: " + cit);
259275

260-
Optional<TypeRef> solved = tryResolveWithSolver(cit);
261-
if (solved.isPresent()) {
262-
return solved;
263-
}
276+
Optional<TypeRef> solved = tryResolveWithSolver(cit);
277+
if (solved.isPresent()) {
278+
return solved;
279+
}
264280

265-
// 2) Best-effort textual fallback (ghost/external)
266-
// getNameWithScope keeps Outer.Inner if present in source, which is better than
267-
// simple name.
268-
logger.log(Level.INFO, () -> "Textual: " + cit.getNameWithScope());
281+
String fallbackName = cit.getNameWithScope(); // may be Outer.Inner
282+
logger.log(Level.INFO, () -> "Textual: " + fallbackName);
269283

270-
String fallbackName = cit.getNameWithScope();
271-
TypeDeclaration<?> td = getByFqn(fallbackName);
272-
if (td != null) {
273-
logger.log(Level.SEVERE, () -> "QualifiedName dot-dot succeeded on index (2): " + fallbackName);
274-
return Optional.of(new DeclaredTypeRef(td));
284+
TypeDeclaration<?> td = getByFqn(fallbackName);
285+
if (td != null) {
286+
logger.log(Level.INFO, () -> "Index hit: " + fallbackName);
287+
return Optional.of(new DeclaredTypeRef(td));
288+
}
289+
290+
return Optional.of(new UnresolvedTypeRef(fallbackName));
275291
}
276-
logger.log(Level.INFO, () -> "UNSOLVED: " + cit.getNameWithScope());
277-
return Optional.of(new UnresolvedTypeRef(fallbackName));
278292

293+
// Last-resort: keep a name, don’t go empty
294+
// (IntersectionType, UnionType, VarType, UnknownType, etc.)
295+
String label = typeNode.asString();
296+
if (label == null || label.isBlank()) {
297+
label = typeNode.toString();
298+
}
299+
return Optional.of(new UnresolvedTypeRef(label));
279300
}
280301

281302
private Optional<TypeRef> tryResolveWithSolver(ClassOrInterfaceType cit) {

assis-core/src/main/java/io/github/masmangan/assis/internal/DependencyContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public DependencyContext(DeclaredIndex idx, PlantUMLWriter pw, EdgeRegistry er)
3737
this.er = er;
3838
}
3939

40-
public Optional<TypeRef> resolveTarget(Type typeNode, Node usageSite) {
41-
return idx.resolveTarget(typeNode, usageSite);
40+
public Optional<TypeRef> resolveTarget(Type typeNode) {
41+
return idx.resolveTarget(typeNode);
4242
}
4343

4444
/**

assis-core/src/main/java/io/github/masmangan/assis/io/SmartSourceRoot.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
2121
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
2222
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
23+
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
2324
import com.github.javaparser.utils.Log;
2425
import com.github.javaparser.utils.SourceRoot;
2526

@@ -63,17 +64,24 @@ public SmartSourceRoot(Path root) {
6364
this.rootPath = root;
6465

6566
CombinedTypeSolver ts = new CombinedTypeSolver();
66-
// Intentionally source-only: unparsed types remain unresolved (no
67-
// ReflectionTypeSolver).
68-
// FIXME: put solver back!
69-
// ts.add(new ReflectionTypeSolver());
7067

71-
// ts.add(new JavaParserTypeSolver(root));
68+
ts.add(new ReflectionTypeSolver());
69+
70+
ts.add(new JavaParserTypeSolver(root));
7271

7372
JavaSymbolSolver jss = new JavaSymbolSolver(ts);
7473

75-
ParserConfiguration cfg = new ParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17)
76-
.setSymbolResolver(jss);
74+
//@formatter:off
75+
ParserConfiguration cfg = new ParserConfiguration()
76+
.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17)
77+
.setSymbolResolver(jss)
78+
.setLexicalPreservationEnabled(false)
79+
.setAttributeComments(false)
80+
.setDoNotAssignCommentsPrecedingEmptyLines(true)
81+
.setIgnoreAnnotationsWhenAttributingComments(false)
82+
.setStoreTokens(false)
83+
.setDetectOriginalLineSeparator(false);
84+
//@formatter:on
7785

7886
super.setParserConfiguration(cfg);
7987
locked = true;

assis-core/src/test/java/io/github/masmangan/assis/GenerateClassDiagramDependencyCoverageTest.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ class GenerateClassDiagramDependencyCoverageSamplesTest {
2222
@TempDir
2323
Path tempDir;
2424

25-
@Test
26-
void methodBodyDependency() throws Exception {
27-
String puml = generatePumlFromSample("samples/deps/bylocal", tempDir, "bylocal");
28-
29-
assertPumlContainsName(puml, "A");
30-
assertPumlContainsName(puml, "B");
31-
assertAnyLineContainsAll(puml, "p1.A", "..>", "p1.B");
32-
}
25+
// related to visitor bug
26+
// @Test
27+
// void methodBodyDependency() throws Exception {
28+
// String puml = generatePumlFromSample("samples/deps/bylocal", tempDir, "bylocal");
29+
//
30+
// assertPumlContainsName(puml, "A");
31+
// assertPumlContainsName(puml, "B");
32+
// assertAnyLineContainsAll(puml, "p1.A", "..>", "p1.B");
33+
// }
3334

3435
@Test
3536
void methodReturnTypeCreatesDependency() throws Exception {

0 commit comments

Comments
 (0)