Skip to content

Commit 6cee4e3

Browse files
committed
Add API for collecting problems without building AST to ICompilationUnitResolver
Limitations: - The method of collecting Javadoc errors requires converting the AST, so the current implementation for collecting errors using javac doesn't collect Javadoc erros Signed-off-by: David Thompson <davthomp@redhat.com>
1 parent 89eb53f commit 6cee4e3

6 files changed

Lines changed: 375 additions & 84 deletions

File tree

org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Iterator;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Map.Entry;
2627
import java.util.Objects;
2728
import java.util.Optional;
2829
import java.util.function.Function;
@@ -144,6 +145,22 @@ private static Optional<CompilationUnit> findTargetDOM(Map<JavaFileObject, Compi
144145
return Optional.empty();
145146
}
146147
}
148+
149+
private final class CollectDiagnosticsInMap implements DiagnosticListener<JavaFileObject> {
150+
private final JavacProblemConverter problemConverter;
151+
private final List<IProblem> problemCollection;
152+
153+
private CollectDiagnosticsInMap(
154+
JavacProblemConverter problemConverter) {
155+
this.problemConverter = problemConverter;
156+
this.problemCollection = new ArrayList<>();
157+
}
158+
159+
@Override
160+
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
161+
problemCollection.add(problemConverter.createJavacProblem(diagnostic));
162+
}
163+
}
147164

148165
private interface GenericRequestor {
149166
public void acceptBinding(String bindingKey, IBinding binding);
@@ -1105,4 +1122,284 @@ private boolean configureAPIfNecessary(JavacFileManager fileManager) {
11051122

11061123
return false;
11071124
}
1125+
1126+
@Override
1127+
public CompilationUnit findProblems(org.eclipse.jdt.internal.core.CompilationUnit unitElement,
1128+
WorkingCopyOwner workingCopyOwner, Map<String, CategorizedProblem[]> groupedProblems, int astLevel,
1129+
int reconcileFlags, Map<String, String> compilerOptions, boolean resolveBindings, IProgressMonitor monitor)
1130+
throws JavaModelException {
1131+
// Implementation is a hacked together copy of toCompilationUnit
1132+
IJavaProject javaProject = unitElement.getJavaProject();
1133+
var compiler = ToolProvider.getSystemJavaCompiler();
1134+
Context context = new Context();
1135+
CachingJarsJavaFileManager.preRegister(context);
1136+
Map<JavaFileObject, CompilationUnit> filesToUnits = new HashMap<>();
1137+
final UnusedProblemFactory unusedProblemFactory = new UnusedProblemFactory(new DefaultProblemFactory(), compilerOptions);
1138+
var problemConverter = new JavacProblemConverter(compilerOptions, context);
1139+
CollectDiagnosticsInMap diagnosticListener = new CollectDiagnosticsInMap(problemConverter);
1140+
MultiTaskListener.instance(context).add(new TaskListener() {
1141+
@Override
1142+
public void finished(TaskEvent e) {
1143+
if (e.getCompilationUnit() instanceof JCCompilationUnit u) {
1144+
problemConverter.registerUnit(e.getSourceFile(), u);
1145+
}
1146+
1147+
if (e.getKind() == TaskEvent.Kind.ANALYZE) {
1148+
final JavaFileObject file = e.getSourceFile();
1149+
final CompilationUnit dom = filesToUnits.get(file);
1150+
if (dom == null) {
1151+
return;
1152+
}
1153+
1154+
final TypeElement currentTopLevelType = e.getTypeElement();
1155+
UnusedTreeScanner<Void, Void> scanner = new UnusedTreeScanner<>() {
1156+
@Override
1157+
public Void visitClass(ClassTree node, Void p) {
1158+
if (node instanceof JCClassDecl classDecl) {
1159+
/**
1160+
* If a Java file contains multiple top-level types, it will
1161+
* trigger multiple ANALYZE taskEvents for the same compilation
1162+
* unit. Each ANALYZE taskEvent corresponds to the completion
1163+
* of analysis for a single top-level type. Therefore, in the
1164+
* ANALYZE task event listener, we only visit the class and nested
1165+
* classes that belong to the currently analyzed top-level type.
1166+
*/
1167+
if (Objects.equals(currentTopLevelType, classDecl.sym)
1168+
|| !(classDecl.sym.owner instanceof PackageSymbol)) {
1169+
return super.visitClass(node, p);
1170+
} else {
1171+
return null; // Skip if it does not belong to the currently analyzed top-level type.
1172+
}
1173+
}
1174+
1175+
return super.visitClass(node, p);
1176+
}
1177+
};
1178+
final CompilationUnitTree unit = e.getCompilationUnit();
1179+
try {
1180+
scanner.scan(unit, null);
1181+
} catch (Exception ex) {
1182+
ILog.get().error("Internal error when visiting the AST Tree. " + ex.getMessage(), ex);
1183+
}
1184+
1185+
List<CategorizedProblem> unusedProblems = scanner.getUnusedPrivateMembers(unusedProblemFactory);
1186+
if (!unusedProblems.isEmpty()) {
1187+
addProblemsToDOM(dom, unusedProblems);
1188+
}
1189+
1190+
List<CategorizedProblem> unusedImports = scanner.getUnusedImports(unusedProblemFactory);
1191+
List<? extends Tree> topTypes = unit.getTypeDecls();
1192+
int typeCount = topTypes.size();
1193+
// Once all top level types of this Java file have been resolved,
1194+
// we can report the unused import to the DOM.
1195+
if (typeCount <= 1) {
1196+
addProblemsToDOM(dom, unusedImports);
1197+
} else if (typeCount > 1 && topTypes.get(typeCount - 1) instanceof JCClassDecl lastType) {
1198+
if (Objects.equals(currentTopLevelType, lastType.sym)) {
1199+
addProblemsToDOM(dom, unusedImports);
1200+
}
1201+
}
1202+
}
1203+
}
1204+
});
1205+
// must be 1st thing added to context
1206+
context.put(DiagnosticListener.class, diagnosticListener);
1207+
boolean docEnabled = JavaCore.ENABLED.equals(compilerOptions.get(JavaCore.COMPILER_DOC_COMMENT_SUPPORT));
1208+
JavacUtils.configureJavacContext(context, compilerOptions, javaProject, JavacUtils.isTest(javaProject, new org.eclipse.jdt.internal.core.CompilationUnit[] { unitElement }));
1209+
Options.instance(context).put(Option.PROC, "only");
1210+
Optional.ofNullable(Platform.getProduct())
1211+
.map(IProduct::getApplication)
1212+
// if application is not a test runner (so we don't have regressions with JDT test suite because of too many problems
1213+
.or(() -> Optional.ofNullable(System.getProperty("eclipse.application")))
1214+
.filter(name -> !name.contains("test") && !name.contains("junit"))
1215+
// continue as far as possible to get extra warnings about unused
1216+
.ifPresent(id -> Options.instance(context).put("should-stop.ifError", CompileState.GENERATE.toString()));
1217+
var fileManager = (JavacFileManager)context.get(JavaFileManager.class);
1218+
List<JavaFileObject> fileObjects = new ArrayList<>(); // we need an ordered list of them
1219+
File unitFile;
1220+
if (javaProject != null && javaProject.getResource() != null) {
1221+
// path is relative to the workspace, make it absolute
1222+
IResource asResource = javaProject.getProject().getParent().findMember(new String(unitElement.getFileName()));
1223+
if (asResource != null) {
1224+
unitFile = asResource.getLocation().toFile();
1225+
} else {
1226+
unitFile = new File(new String(unitElement.getFileName()));
1227+
}
1228+
} else {
1229+
unitFile = new File(new String(unitElement.getFileName()));
1230+
}
1231+
Path sourceUnitPath = null;
1232+
if (!unitFile.getName().endsWith(".java") || unitElement.getFileName() == null || unitElement.getFileName().length == 0) {
1233+
String uri1 = unitFile.toURI().toString().replaceAll("%7C", "/");
1234+
if( uri1.endsWith(".class")) {
1235+
String[] split= uri1.split("/");
1236+
String lastSegment = split[split.length-1].replace(".class", ".java");
1237+
sourceUnitPath = Path.of(lastSegment);
1238+
}
1239+
if( sourceUnitPath == null )
1240+
sourceUnitPath = Path.of(new File("whatever.java").toURI());
1241+
} else {
1242+
sourceUnitPath = Path.of(unitFile.toURI());
1243+
}
1244+
var fileObject = fileManager.getJavaFileObject(sourceUnitPath);
1245+
fileManager.cache(fileObject, CharBuffer.wrap(unitElement.getContents()));
1246+
1247+
CompilationUnit res = null;
1248+
if (astLevel != ICompilationUnit.NO_AST) {
1249+
AST ast = createAST(compilerOptions, astLevel, context, reconcileFlags);
1250+
res = ast.newCompilationUnit();
1251+
filesToUnits.put(fileObject, res);
1252+
}
1253+
fileObjects.add(fileObject);
1254+
1255+
1256+
JCCompilationUnit javacCompilationUnit = null;
1257+
Iterable<String> options = configureAPIfNecessary(fileManager) ? null : Arrays.asList("-proc:none");
1258+
JavacTask task = ((JavacTool)compiler).getTask(null, fileManager, null /* already added to context */, options, List.of() /* already set */, fileObjects, context);
1259+
{
1260+
// don't know yet a better way to ensure those necessary flags get configured
1261+
var javac = com.sun.tools.javac.main.JavaCompiler.instance(context);
1262+
javac.keepComments = true;
1263+
javac.genEndPos = true;
1264+
javac.lineDebugInfo = true;
1265+
}
1266+
1267+
List<JCCompilationUnit> javacCompilationUnits = new ArrayList<>();
1268+
try {
1269+
var elements = task.parse().iterator();
1270+
var aptPath = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH);
1271+
if ((reconcileFlags & ICompilationUnit.FORCE_PROBLEM_DETECTION) != 0
1272+
|| (aptPath != null && aptPath.iterator().hasNext())) {
1273+
task.analyze();
1274+
}
1275+
1276+
Throwable cachedThrown = null;
1277+
1278+
if (elements.hasNext() && elements.next() instanceof JCCompilationUnit u) {
1279+
javacCompilationUnit = u;
1280+
javacCompilationUnits.add(u);
1281+
} else {
1282+
throw new IllegalStateException("expected a compiled cu");
1283+
}
1284+
try {
1285+
String rawText = null;
1286+
try {
1287+
rawText = fileObject.getCharContent(true).toString();
1288+
} catch( IOException ioe) {
1289+
ILog.get().error(ioe.getMessage(), ioe);
1290+
return null;
1291+
}
1292+
JavacConverter converter = null;
1293+
if (astLevel != ICompilationUnit.NO_AST) {
1294+
AST ast = res.ast;
1295+
converter = new JavacConverter(ast, javacCompilationUnit, context, rawText, docEnabled, -1);
1296+
converter.populateCompilationUnit(res, javacCompilationUnit);
1297+
// javadoc problems explicitly set as they're not sent to DiagnosticListener (maybe find a flag to do it?)
1298+
var javadocProblems = converter.javadocDiagnostics.stream()
1299+
.map(problemConverter::createJavacProblem)
1300+
.filter(Objects::nonNull)
1301+
.toArray(IProblem[]::new);
1302+
if (javadocProblems.length > 0) {
1303+
int initialSize = res.getProblems().length;
1304+
var newProblems = Arrays.copyOf(res.getProblems(), initialSize + javadocProblems.length);
1305+
System.arraycopy(javadocProblems, 0, newProblems, initialSize, javadocProblems.length);
1306+
res.setProblems(newProblems);
1307+
}
1308+
List<org.eclipse.jdt.core.dom.Comment> javadocComments = new ArrayList<>();
1309+
res.accept(new ASTVisitor(true) {
1310+
@Override
1311+
public void postVisit(ASTNode node) { // fix some positions
1312+
if( node.getParent() != null ) {
1313+
if( node.getStartPosition() < node.getParent().getStartPosition()) {
1314+
int parentEnd = node.getParent().getStartPosition() + node.getParent().getLength();
1315+
if( node.getStartPosition() >= 0 ) {
1316+
node.getParent().setSourceRange(node.getStartPosition(), parentEnd - node.getStartPosition());
1317+
}
1318+
}
1319+
}
1320+
}
1321+
@Override
1322+
public boolean visit(Javadoc javadoc) {
1323+
javadocComments.add(javadoc);
1324+
return true;
1325+
}
1326+
});
1327+
addCommentsToUnit(javadocComments, res);
1328+
addCommentsToUnit(converter.notAttachedComments, res);
1329+
attachMissingComments(res, context, rawText, converter, compilerOptions);
1330+
if ((reconcileFlags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) == 0) {
1331+
// remove all possible RECOVERED node
1332+
res.accept(new ASTVisitor(false) {
1333+
private boolean reject(ASTNode node) {
1334+
return (node.getFlags() & ASTNode.RECOVERED) != 0
1335+
|| (node instanceof FieldDeclaration field && field.fragments().isEmpty())
1336+
|| (node instanceof VariableDeclarationStatement decl && decl.fragments().isEmpty());
1337+
}
1338+
1339+
@Override
1340+
public boolean preVisit2(ASTNode node) {
1341+
if (reject(node)) {
1342+
StructuralPropertyDescriptor prop = node.getLocationInParent();
1343+
if ((prop instanceof SimplePropertyDescriptor simple && !simple.isMandatory())
1344+
|| (prop instanceof ChildPropertyDescriptor child && !child.isMandatory())
1345+
|| (prop instanceof ChildListPropertyDescriptor)) {
1346+
node.delete();
1347+
} else if (node.getParent() != null) {
1348+
node.getParent().setFlags(node.getParent().getFlags() | ASTNode.RECOVERED);
1349+
}
1350+
return false; // branch will be cut, no need to inspect deeper
1351+
}
1352+
return true;
1353+
}
1354+
1355+
@Override
1356+
public void postVisit(ASTNode node) {
1357+
// repeat on postVisit so trimming applies bottom-up
1358+
preVisit2(node);
1359+
}
1360+
});
1361+
}
1362+
}
1363+
if (resolveBindings) {
1364+
JavacBindingResolver resolver = new JavacBindingResolver(javaProject, task, context, converter, workingCopyOwner, javacCompilationUnits);
1365+
resolver.isRecoveringBindings = (reconcileFlags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0;
1366+
if (astLevel != ICompilationUnit.NO_AST) {
1367+
res.ast.setBindingResolver(resolver);
1368+
}
1369+
}
1370+
//
1371+
if (astLevel != ICompilationUnit.NO_AST) {
1372+
res.ast.setOriginalModificationCount(res.ast.modificationCount()); // "un-dirty" AST so Rewrite can process it
1373+
res.ast.setDefaultNodeFlag(res.ast.getDefaultNodeFlag() & ~ASTNode.ORIGINAL);
1374+
}
1375+
} catch (Throwable thrown) {
1376+
if (cachedThrown == null) {
1377+
cachedThrown = thrown;
1378+
}
1379+
ILog.get().error("Internal failure while parsing or converting AST for unit " + new String(unitElement.getFileName()));
1380+
ILog.get().error(thrown.getMessage(), thrown);
1381+
}
1382+
if (!resolveBindings) {
1383+
destroy(context);
1384+
}
1385+
if (cachedThrown != null) {
1386+
throw new RuntimeException(cachedThrown);
1387+
}
1388+
} catch (IOException ex) {
1389+
ILog.get().error(ex.getMessage(), ex);
1390+
}
1391+
1392+
// compute grouped problems
1393+
Map<String, List<CategorizedProblem>> tempGroupedProblems = new HashMap<>();
1394+
for (IProblem problem : diagnosticListener.problemCollection) {
1395+
if (problem instanceof CategorizedProblem categorizedProblem) {
1396+
tempGroupedProblems.computeIfAbsent(categorizedProblem.getMarkerType(), key -> new ArrayList<>()).add(categorizedProblem);
1397+
}
1398+
}
1399+
for (Entry<String, List<CategorizedProblem>> entry : tempGroupedProblems.entrySet()) {
1400+
groupedProblems.put(entry.getKey(), entry.getValue().toArray(CategorizedProblem[]::new));
1401+
}
1402+
1403+
return res;
1404+
}
11081405
}

org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/CompilationUnitResolverDiscoveryTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.eclipse.jdt.core.JavaCore;
2323
import org.eclipse.jdt.core.JavaModelException;
2424
import org.eclipse.jdt.core.WorkingCopyOwner;
25+
import org.eclipse.jdt.core.compiler.CategorizedProblem;
2526
import org.eclipse.jdt.core.dom.AST;
2627
import org.eclipse.jdt.core.dom.ASTNode;
2728
import org.eclipse.jdt.core.dom.ASTRequestor;
@@ -187,5 +188,14 @@ public void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, A
187188
WorkingCopyOwner workingCopyOwner, int flags, IProgressMonitor monitor) {
188189
// irrelevant for test
189190
}
191+
192+
@Override
193+
public CompilationUnit findProblems(org.eclipse.jdt.internal.core.CompilationUnit unitElement,
194+
WorkingCopyOwner workingCopyOwner, Map<String, CategorizedProblem[]> problems, int astLevel,
195+
int reconcileFlags, Map<String, String> options, boolean resolveBindings, IProgressMonitor monitor)
196+
throws JavaModelException {
197+
// irrelevant for test
198+
return null;
199+
}
190200
}
191201
}

org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,32 @@ public CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.I
106106
return CompilationUnitResolver.toCompilationUnit(sourceUnit, initialNeedsToResolveBinding, project,
107107
classpaths, focalPosition == -1 ? null : new NodeSearcher(focalPosition), apiLevel, compilerOptions, parsedUnitWorkingCopyOwner, typeRootWorkingCopyOwner, flags, monitor);
108108
}
109+
110+
@Override
111+
public CompilationUnit findProblems(org.eclipse.jdt.internal.core.CompilationUnit unitElement, WorkingCopyOwner workingCopyOwner,
112+
Map<String, CategorizedProblem[]> problems, int astLevel, int reconcileFlags, Map<String, String> options, boolean resolveBindings,
113+
IProgressMonitor monitor) throws JavaModelException {
114+
CompilationUnitDeclaration cud = null;
115+
try {
116+
cud = CompilationUnitProblemFinder.process(unitElement, workingCopyOwner, problems, astLevel != ICompilationUnit.NO_AST, reconcileFlags, monitor);
117+
if (monitor != null) monitor.worked(1);
118+
if (astLevel != ICompilationUnit.NO_AST && cud != null) {
119+
return AST.convertCompilationUnit(
120+
astLevel,
121+
cud,
122+
options,
123+
resolveBindings,
124+
unitElement,
125+
reconcileFlags,
126+
monitor);
127+
}
128+
return null;
129+
} finally {
130+
if (cud != null) {
131+
cud.cleanUp();
132+
}
133+
}
134+
}
109135
}
110136

111137
private static ECJCompilationUnitResolver FACADE;

org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolverDiscovery.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
import org.eclipse.jdt.core.JavaCore;
2121
import org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver;
2222

23-
class CompilationUnitResolverDiscovery {
23+
public class CompilationUnitResolverDiscovery {
2424
private static final String SELECTED_SYSPROP = "ICompilationUnitResolver"; //$NON-NLS-1$
2525
private static final String COMPILATION_UNIT_RESOLVER_EXTPOINT_ID = "compilationUnitResolver" ; //$NON-NLS-1$
2626
private static boolean ERROR_LOGGED = false;
2727

2828
private static String lastId;
2929
private static IConfigurationElement lastExtension;
3030

31-
static ICompilationUnitResolver getInstance() {
31+
public static ICompilationUnitResolver getInstance() {
3232
String id = System.getProperty(SELECTED_SYSPROP);
3333
IConfigurationElement configElement = getConfigurationElement(id);
3434
lastId = id;

0 commit comments

Comments
 (0)