From 3a12d40b075cb3738947afbf0461c4b8f54d2177 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Mon, 29 Jan 2024 11:52:37 +0100 Subject: [PATCH 01/26] ASTParser.createAST looses ability to resolve some bindings Having root unit set in the lookupEnvironment (when focusing on a single unit) can be necessary for further binding resolution. Fixes https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1915 --- .../core/tests/dom/ASTConverter15Test.java | 36 +++++++++++++++---- .../jdt/core/dom/CompilationUnitResolver.java | 3 ++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15Test.java index fde21f5564a..d6ffcd74e68 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15Test.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15Test.java @@ -27,6 +27,7 @@ import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.BindingKey; import org.eclipse.jdt.core.IAnnotation; @@ -7866,13 +7867,13 @@ public void test0238() throws JavaModelException { * https://bugs.eclipse.org/bugs/show_bug.cgi?id=173338 */ public void test0238_2() throws JavaModelException { - this.workingCopy = getWorkingCopy("/Converter15/src/test0238/X.java", true/*resolve*/); - String contents = - "package test0238;\n" + - "public class X extends A {\n" + - "}"; + this.workingCopy = getWorkingCopy("/Converter15/src/test0238/X.java", + """ + package test0238; + public class X extends A { + }""", true/*resolve*/); ASTNode node = buildAST( - contents, + this.workingCopy.getSource(), this.workingCopy, false, false, @@ -7891,6 +7892,29 @@ public void test0238_2() throws JavaModelException { assertEquals("wrong size", 2, methodBindings.length); } + /* + * https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1915 + */ + public void test0238_ASTParser() throws JavaModelException, CoreException { + this.workingCopy = getWorkingCopy("/Converter15/src/test0238/X.java", + """ + package test0238; + public class X extends A { + } + """, true /*resolve*/); + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setBindingsRecovery(true); + parser.setResolveBindings(true); + parser.setStatementsRecovery(true); + parser.setSource(this.workingCopy); + parser.setProject(JavaCore.create(ResourcesPlugin.getWorkspace().getRoot().getProject("Converter15"))); + CompilationUnit unit = (CompilationUnit) parser.createAST(null); + TypeDeclaration typeDeclaration = (TypeDeclaration) unit.types().get(0); + ITypeBinding superTypeBinding = typeDeclaration.resolveBinding().getSuperclass(); + IMethodBinding[] methodBindings = superTypeBinding.getDeclaredMethods(); + assertEquals("wrong size", 2, methodBindings.length); + } + /* * https://bugs.eclipse.org/bugs/show_bug.cgi?id=173338 */ diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java index 4fc1869efba..7a6e5f24b84 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java @@ -1261,6 +1261,9 @@ private CompilationUnitDeclaration resolve( if (unit == null) { unit = this.unitsToProcess[0]; // fall back to old behavior } + if (this.totalUnits == 1 && this.lookupEnvironment.unitBeingCompleted == null) { + this.lookupEnvironment.unitBeingCompleted = unit; + } } else { // initial type binding creation this.lookupEnvironment.buildTypeBindings(unit, null /*no access restriction*/); From ae5076d826c2241de8224323394380c44791cfa2 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Fri, 12 Jan 2024 20:20:13 +0100 Subject: [PATCH 02/26] Switch to make some operations rely on DOM instead of ECJ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduced 2 system property switches to control whether some IDE operations are using ECJ parser (legacy/default) or whether to make those operations powered by a full DOM. * CompilationUnit.DOM_BASED_OPERATIONS will make codeSelect (hover, link to definition...), buildStructure (JDT Project element model), reconciler (code diagnostics feedback); this one seems currently working ✔ * CompilationUnit.codeComplete.DOM_BASED_OPERATIONS controls completion (unlike other operations, completion based on DOM is far from being complete or straightforward) 🏗️ * SourceIndexer.DOM_BASED_INDEXER controls whether the indexation of a source document should first build/use a full DOM. This one is currently incomplete 🏗️ The DOM-based operation can then allow to plug alternative compiler then ECJ and still get JDT functionalities working. --- .../jdt/internal/core/ASTHolderCUInfo.java | 2 +- .../jdt/internal/core/CompilationUnit.java | 243 ++++-- .../internal/core/DOMCompletionEngine.java | 188 +++++ .../internal/core/DOMToModelPopulator.java | 700 ++++++++++++++++++ .../core/ReconcileWorkingCopyOperation.java | 84 ++- .../core/search/JavaSearchDocument.java | 2 +- .../search/indexing/DOMToIndexVisitor.java | 134 ++++ .../core/search/indexing/SourceIndexer.java | 46 ++ 8 files changed, 1297 insertions(+), 102 deletions(-) create mode 100644 org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMCompletionEngine.java create mode 100644 org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java create mode 100644 org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/DOMToIndexVisitor.java diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ASTHolderCUInfo.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ASTHolderCUInfo.java index cae44607b34..37e3571a7ed 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ASTHolderCUInfo.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ASTHolderCUInfo.java @@ -19,7 +19,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit; public class ASTHolderCUInfo extends CompilationUnitElementInfo { - int astLevel; + public int astLevel; boolean resolveBindings; int reconcileFlags; Map problems = null; diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index 807339434c3..13b3d597ecf 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -24,6 +24,16 @@ import org.eclipse.jdt.core.*; import org.eclipse.jdt.core.compiler.*; import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.internal.compiler.IProblemFactory; import org.eclipse.jdt.internal.compiler.SourceElementParser; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; @@ -46,11 +56,23 @@ * @see ICompilationUnit */ public class CompilationUnit extends Openable implements ICompilationUnit, org.eclipse.jdt.internal.compiler.env.ICompilationUnit, SuffixConstants { + /** + * Internal synonym for deprecated constant AST.JSL2 + * to alleviate deprecation warnings. + * @deprecated + */ + /*package*/ static final int JLS2_INTERNAL = AST.JLS2; + + public static boolean DOM_BASED_OPERATIONS = Boolean.getBoolean(CompilationUnit.class.getSimpleName() + ".DOM_BASED_OPERATIONS"); //$NON-NLS-1$ + public static boolean DOM_BASED_COMPLETION = Boolean.getBoolean(CompilationUnit.class.getSimpleName() + ".codeComplete.DOM_BASED_OPERATIONS"); //$NON-NLS-1$ + private static final IImportDeclaration[] NO_IMPORTS = new IImportDeclaration[0]; protected final String name; public final WorkingCopyOwner owner; + private org.eclipse.jdt.core.dom.CompilationUnit ast; + /** * Constructs a handle to a compilation unit with the given name in the * specified package for the specified owner @@ -101,36 +123,13 @@ public void becomeWorkingCopy(IProgressMonitor monitor) throws JavaModelExceptio protected boolean buildStructure(OpenableElementInfo info, final IProgressMonitor pm, Map newElements, IResource underlyingResource) throws JavaModelException { CompilationUnitElementInfo unitInfo = (CompilationUnitElementInfo) info; - // ensure buffer is opened - IBuffer buffer = getBufferManager().getBuffer(CompilationUnit.this); - if (buffer == null) { - openBuffer(pm, unitInfo); // open buffer independently from the info, since we are building the info - } - // generate structure and compute syntax problems if needed - CompilationUnitStructureRequestor requestor = new CompilationUnitStructureRequestor(this, unitInfo, newElements); JavaModelManager.PerWorkingCopyInfo perWorkingCopyInfo = getPerWorkingCopyInfo(); IJavaProject project = getJavaProject(); - - boolean createAST; - boolean resolveBindings; - int reconcileFlags; - Map problems; - if (info instanceof ASTHolderCUInfo) { - ASTHolderCUInfo astHolder = (ASTHolderCUInfo) info; - createAST = astHolder.astLevel != NO_AST; - resolveBindings = astHolder.resolveBindings; - reconcileFlags = astHolder.reconcileFlags; - problems = astHolder.problems; - } else { - createAST = false; - resolveBindings = false; - reconcileFlags = 0; - problems = null; - } - + boolean createAST = info instanceof ASTHolderCUInfo astHolder ? astHolder.astLevel != NO_AST : false; + boolean resolveBindings = info instanceof ASTHolderCUInfo astHolder ? astHolder.resolveBindings : false; + int reconcileFlags = info instanceof ASTHolderCUInfo astHolder ? astHolder.reconcileFlags : 0; boolean computeProblems = perWorkingCopyInfo != null && perWorkingCopyInfo.isActive() && project != null && JavaProject.hasJavaNature(project.getProject()); - IProblemFactory problemFactory = new DefaultProblemFactory(); Map options = this.getOptions(true); if (!computeProblems) { // disable task tags checking to speed up parsing @@ -138,67 +137,106 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito } CompilerOptions compilerOptions = new CompilerOptions(options); compilerOptions.ignoreMethodBodies = (reconcileFlags & ICompilationUnit.IGNORE_METHOD_BODIES) != 0; - SourceElementParser parser = new SourceElementParser( - requestor, - problemFactory, - compilerOptions, - true/*report local declarations*/, - !createAST /*optimize string literals only if not creating a DOM AST*/); - parser.reportOnlyOneSyntaxError = !computeProblems; - parser.setMethodsFullRecovery(true); - parser.setStatementsRecovery((reconcileFlags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); - - if (!computeProblems && !resolveBindings && !createAST) // disable javadoc parsing if not computing problems, not resolving and not creating ast - parser.javadocParser.checkDocComment = false; - requestor.parser = parser; // update timestamp (might be IResource.NULL_STAMP if original does not exist) if (underlyingResource == null) { underlyingResource = getResource(); } // underlying resource is null in the case of a working copy on a class file in a jar - if (underlyingResource != null) + if (underlyingResource != null) { unitInfo.timestamp = ((IFile)underlyingResource).getModificationStamp(); + } - // compute other problems if needed - CompilationUnitDeclaration compilationUnitDeclaration = null; - CompilationUnit source = cloneCachingContents(); - try { - if (computeProblems) { - if (problems == null) { - // report problems to the problem requestor - problems = new HashMap<>(); - compilationUnitDeclaration = CompilationUnitProblemFinder.process(source, parser, this.owner, problems, createAST, reconcileFlags, pm); + // ensure buffer is opened + IBuffer buffer = getBufferManager().getBuffer(CompilationUnit.this); + if (buffer == null) { + openBuffer(pm, unitInfo); // open buffer independently from the info, since we are building the info + } + + if (DOM_BASED_OPERATIONS) { + ASTParser astParser = ASTParser.newParser(info instanceof ASTHolderCUInfo astHolder && astHolder.astLevel > 0 ? astHolder.astLevel : AST.getJLSLatest()); + astParser.setWorkingCopyOwner(getOwner()); + astParser.setSource(this); + astParser.setProject(getJavaProject()); + astParser.setStatementsRecovery((reconcileFlags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); + astParser.setResolveBindings(resolveBindings); + astParser.setBindingsRecovery((reconcileFlags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0); + if (astParser.createAST(pm) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { + if (perWorkingCopyInfo != null) { try { perWorkingCopyInfo.beginReporting(); - for (Iterator iteraror = problems.values().iterator(); iteraror.hasNext();) { - CategorizedProblem[] categorizedProblems = iteraror.next(); - if (categorizedProblems == null) continue; - for (int i = 0, length = categorizedProblems.length; i < length; i++) { - perWorkingCopyInfo.acceptProblem(categorizedProblems[i]); - } + for (IProblem problem : newAST.getProblems()) { + perWorkingCopyInfo.acceptProblem(problem); } } finally { perWorkingCopyInfo.endReporting(); } - } else { - // collect problems - compilationUnitDeclaration = CompilationUnitProblemFinder.process(source, parser, this.owner, problems, createAST, reconcileFlags, pm); } - } else { - compilationUnitDeclaration = parser.parseCompilationUnit(source, true /*full parse to find local elements*/, pm); + if (info instanceof ASTHolderCUInfo astHolder) { + astHolder.ast = newAST; + } + newAST.accept(new DOMToModelPopulator(newElements, this, unitInfo)); + // unitInfo.setModule(); + // unitInfo.setSourceLength(newSourceLength); + unitInfo.setIsStructureKnown(true); } + } else { + CompilationUnitStructureRequestor requestor = new CompilationUnitStructureRequestor(this, unitInfo, newElements); + Map problems = info instanceof ASTHolderCUInfo astHolder ? astHolder.problems : null; + IProblemFactory problemFactory = new DefaultProblemFactory(); + SourceElementParser parser = new SourceElementParser( + requestor, + problemFactory, + compilerOptions, + true/*report local declarations*/, + !createAST /*optimize string literals only if not creating a DOM AST*/); + parser.reportOnlyOneSyntaxError = !computeProblems; + parser.setMethodsFullRecovery(true); + parser.setStatementsRecovery((reconcileFlags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); + + if (!computeProblems && !resolveBindings && !createAST) // disable javadoc parsing if not computing problems, not resolving and not creating ast + parser.javadocParser.checkDocComment = false; + requestor.parser = parser; + + // compute other problems if needed + CompilationUnitDeclaration compilationUnitDeclaration = null; + CompilationUnit source = cloneCachingContents(); + try { + if (computeProblems) { + if (problems == null) { + // report problems to the problem requestor + problems = new HashMap<>(); + compilationUnitDeclaration = CompilationUnitProblemFinder.process(source, parser, this.owner, problems, createAST, reconcileFlags, pm); + try { + perWorkingCopyInfo.beginReporting(); + for (CategorizedProblem[] categorizedProblems : problems.values()) { + if (categorizedProblems == null) continue; + for (CategorizedProblem categorizedProblem : categorizedProblems) { + perWorkingCopyInfo.acceptProblem(categorizedProblem); + } + } + } finally { + perWorkingCopyInfo.endReporting(); + } + } else { + // collect problems + compilationUnitDeclaration = CompilationUnitProblemFinder.process(source, parser, this.owner, problems, createAST, reconcileFlags, pm); + } + } else { + compilationUnitDeclaration = parser.parseCompilationUnit(source, true /*full parse to find local elements*/, pm); + } - if (createAST) { - int astLevel = ((ASTHolderCUInfo) info).astLevel; - org.eclipse.jdt.core.dom.CompilationUnit cu = AST.convertCompilationUnit(astLevel, compilationUnitDeclaration, options, computeProblems, source, reconcileFlags, pm); - ((ASTHolderCUInfo) info).ast = cu; + if (createAST) { + int astLevel = ((ASTHolderCUInfo) info).astLevel; + org.eclipse.jdt.core.dom.CompilationUnit cu = AST.convertCompilationUnit(astLevel, compilationUnitDeclaration, options, computeProblems, source, reconcileFlags, pm); + ((ASTHolderCUInfo) info).ast = cu; + } + } finally { + if (compilationUnitDeclaration != null) { + unitInfo.hasFunctionalTypes = compilationUnitDeclaration.hasFunctionalTypes(); + compilationUnitDeclaration.cleanUp(); + } } - } finally { - if (compilationUnitDeclaration != null) { - unitInfo.hasFunctionalTypes = compilationUnitDeclaration.hasFunctionalTypes(); - compilationUnitDeclaration.cleanUp(); - } } return unitInfo.isStructureKnown(); @@ -357,6 +395,10 @@ public void codeComplete(int offset, CompletionRequestor requestor, WorkingCopyO @Override public void codeComplete(int offset, CompletionRequestor requestor, WorkingCopyOwner workingCopyOwner, IProgressMonitor monitor) throws JavaModelException { + if (DOM_BASED_COMPLETION) { + new DOMCompletionEngine(offset, getOrBuildAST(workingCopyOwner), requestor, monitor).run(); + return; + } codeComplete( this, isWorkingCopy() ? (org.eclipse.jdt.internal.compiler.env.ICompilationUnit) getOriginalElement() : this, @@ -379,8 +421,67 @@ public IJavaElement[] codeSelect(int offset, int length) throws JavaModelExcepti */ @Override public IJavaElement[] codeSelect(int offset, int length, WorkingCopyOwner workingCopyOwner) throws JavaModelException { - return super.codeSelect(this, offset, length, workingCopyOwner); + if (DOM_BASED_OPERATIONS) { + org.eclipse.jdt.core.dom.CompilationUnit currentAST = getOrBuildAST(workingCopyOwner); + if (currentAST == null) { + return new IJavaElement[0]; + } + ASTNode node = NodeFinder.perform(currentAST, offset, length); + IBinding binding = resolveBinding(node); + if (binding != null) { + return new IJavaElement[] { binding.getJavaElement() }; + } + return new IJavaElement[0]; + } else { + return super.codeSelect(this, offset, length, workingCopyOwner); + } } +static IBinding resolveBinding(ASTNode node) { + if (node instanceof MethodDeclaration decl) { + return decl.resolveBinding(); + } + if (node instanceof MethodInvocation invocation) { + return invocation.resolveMethodBinding(); + } + if (node instanceof VariableDeclaration decl) { + return decl.resolveBinding(); + } + if (node instanceof FieldAccess access) { + return access.resolveFieldBinding(); + } + if (node instanceof Type type) { + return type.resolveBinding(); + } + if (node instanceof Name aName) { + IBinding res = aName.resolveBinding(); + if (res != null) { + return res; + } + return resolveBinding(aName.getParent()); + } + if (node instanceof org.eclipse.jdt.core.dom.TypeParameter typeParameter) { + return typeParameter.resolveBinding(); + } + return null; +} + + +private org.eclipse.jdt.core.dom.CompilationUnit getOrBuildAST(WorkingCopyOwner workingCopyOwner) { + if (this.ast == null) { + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); // TODO use Java project info + parser.setSource(this); + // greedily enable everything assuming the AST will be used extensively for edition + parser.setResolveBindings(true); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + if (parser.createAST(null) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { + this.ast = newAST; + } + } + return this.ast; +} + + /** * @see IWorkingCopy#commit(boolean, IProgressMonitor) * @deprecated @@ -1137,7 +1238,7 @@ public org.eclipse.jdt.core.dom.CompilationUnit makeConsistent(int astLevel, boo openWhenClosed(info, true, monitor); org.eclipse.jdt.core.dom.CompilationUnit result = info.ast; info.ast = null; - return result; + return astLevel != NO_AST ? result : null; } else { openWhenClosed(createElementInfo(), true, monitor); return null; diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMCompletionEngine.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMCompletionEngine.java new file mode 100644 index 00000000000..a1923cda9a5 --- /dev/null +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMCompletionEngine.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.core; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.CompletionContext; +import org.eclipse.jdt.core.CompletionProposal; +import org.eclipse.jdt.core.CompletionRequestor; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.Statement; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; + +class DOMCompletionEngine implements Runnable { + + private final int offset; + private final CompilationUnit unit; + private CompletionRequestor requestor; + + DOMCompletionEngine(int offset, CompilationUnit unit, CompletionRequestor requestor, IProgressMonitor monitor) { + this.offset = offset; + this.unit = unit; + this.requestor = requestor; + } + + private static Collection visibleBindings(ASTNode node, int offset) { + if (node instanceof Block block) { + return ((List)block.statements()).stream() + .filter(statement -> statement.getStartPosition() < offset) + .filter(VariableDeclarationStatement.class::isInstance) + .map(VariableDeclarationStatement.class::cast) + .flatMap(decl -> ((List)decl.fragments()).stream()) + .map(VariableDeclarationFragment::resolveBinding) + .toList(); + } else if (node instanceof MethodDeclaration method) { + return Stream.of((List)method.parameters(), (List)method.typeParameters()) + .flatMap(List::stream) + .map(org.eclipse.jdt.internal.core.CompilationUnit::resolveBinding) + .filter(Objects::nonNull) + .toList(); + } else if (node instanceof TypeDeclaration type) { + VariableDeclarationFragment[] fields = Arrays.stream(type.getFields()) + .map(decl -> (List)decl.fragments()) + .flatMap(List::stream) + .toArray(VariableDeclarationFragment[]::new); + return Stream.of(fields, type.getMethods(), type.getTypes()) + .flatMap(Arrays::stream) + .map(org.eclipse.jdt.internal.core.CompilationUnit::resolveBinding) + .filter(Objects::nonNull) + .toList(); + } + return List.of(); + } + + @Override + public void run() { + this.requestor.beginReporting(); + this.requestor.acceptContext(new CompletionContext()); + ASTNode toComplete = NodeFinder.perform(this.unit, this.offset, 0); + if (toComplete instanceof FieldAccess fieldAccess) { + processMembers(fieldAccess.resolveTypeBinding()); + } else if (toComplete.getParent() instanceof FieldAccess fieldAccess) { + processMembers(fieldAccess.getExpression().resolveTypeBinding()); + } + Collection scope = new HashSet<>(); + ASTNode current = toComplete; + while (current != null) { + scope.addAll(visibleBindings(current, this.offset)); + current = current.getParent(); + } + // TODO also include other visible content: classpath, static methods... + scope.stream().map(this::toProposal).forEach(this.requestor::accept); + this.requestor.endReporting(); + } + + private void processMembers(ITypeBinding typeBinding) { + if (typeBinding == null) { + return; + } + Arrays.stream(typeBinding.getDeclaredFields()).map(this::toProposal).forEach(this.requestor::accept); + Arrays.stream(typeBinding.getDeclaredMethods()).map(this::toProposal).forEach(this.requestor::accept); + if (typeBinding.getInterfaces() != null) { + Arrays.stream(typeBinding.getInterfaces()).forEach(this::processMembers); + } + processMembers(typeBinding.getSuperclass()); + } + + private CompletionProposal toProposal(IBinding binding) { + int kind = + binding instanceof ITypeBinding ? CompletionProposal.TYPE_REF : + binding instanceof IMethodBinding ? CompletionProposal.METHOD_REF : + binding instanceof IVariableBinding variableBinding ? CompletionProposal.LOCAL_VARIABLE_REF : + -1; + CompletionProposal res = new CompletionProposal() { + @Override + public int getKind() { + return kind; + } + @Override + public char[] getName() { + return binding.getName().toCharArray(); + } + @Override + public char[] getCompletion() { + return binding.getName().toCharArray(); + } + @Override + public char[] getSignature() { + if (binding instanceof IMethodBinding methodBinding) { + return Signature.createMethodSignature( + Arrays.stream(methodBinding.getParameterTypes()) + .map(ITypeBinding::getName) + .map(String::toCharArray) + .map(type -> Signature.createTypeSignature(type, true).toCharArray()) + .toArray(char[][]::new), + Signature.createTypeSignature(methodBinding.getReturnType().getQualifiedName().toCharArray(), true).toCharArray()); + } + if (binding instanceof IVariableBinding variableBinding) { + return Signature.createTypeSignature(variableBinding.getType().getQualifiedName().toCharArray(), true).toCharArray(); + } + if (binding instanceof ITypeBinding typeBinding) { + return Signature.createTypeSignature(typeBinding.getQualifiedName().toCharArray(), true).toCharArray(); + } + return new char[] {}; + } + @Override + public int getReplaceStart() { + return DOMCompletionEngine.this.offset; + } + @Override + public int getReplaceEnd() { + return getReplaceStart(); + } + @Override + public int getFlags() { + return 0; //TODO + } + @Override + public char[] getReceiverSignature() { + if (binding instanceof IMethodBinding method) { + return Signature.createTypeSignature(method.getDeclaredReceiverType().getQualifiedName().toCharArray(), true).toCharArray(); + } + if (binding instanceof IVariableBinding variable && variable.isField()) { + return Signature.createTypeSignature(variable.getDeclaringClass().getQualifiedName().toCharArray(), true).toCharArray(); + } + return new char[]{}; + } + @Override + public char[] getDeclarationSignature() { + if (binding instanceof IMethodBinding method) { + return Signature.createTypeSignature(method.getDeclaringClass().getQualifiedName().toCharArray(), true).toCharArray(); + } + if (binding instanceof IVariableBinding variable && variable.isField()) { + return Signature.createTypeSignature(variable.getDeclaringClass().getQualifiedName().toCharArray(), true).toCharArray(); + } + return new char[]{}; + } + }; + return res; + } + +} diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java new file mode 100644 index 00000000000..215f1c1fe3e --- /dev/null +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -0,0 +1,700 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.core; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.AbstractMap.SimpleEntry; +import java.util.Map.Entry; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.IAnnotation; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMemberValuePair; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.compiler.InvalidInputException; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.BooleanLiteral; +import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.ExportsDirective; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.Initializer; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.ModuleDeclaration; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.NullLiteral; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.OpensDirective; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.ProvidesDirective; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.RecordDeclaration; +import org.eclipse.jdt.core.dom.RequiresDirective; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jdt.core.dom.UsesDirective; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.env.IElementInfo; +import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; +import org.eclipse.jdt.internal.compiler.parser.Scanner; +import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; +import org.eclipse.jdt.internal.core.ModuleDescriptionInfo.ModuleReferenceInfo; +import org.eclipse.jdt.internal.core.ModuleDescriptionInfo.PackageExportInfo; +import org.eclipse.jdt.internal.core.ModuleDescriptionInfo.ServiceInfo; +import org.eclipse.jdt.internal.core.util.Util; + +/** + * Process an AST to populate a tree of IJavaElement->JavaElementInfo. + * DOM-first approach to what legacy implements through ECJ parser and CompilationUnitStructureRequestor + */ +class DOMToModelPopulator extends ASTVisitor { + + private final Map toPopulate; + private final Stack elements = new Stack<>(); + private final Stack infos = new Stack<>(); + private final Set currentTypeParameters = new HashSet<>(); + private final CompilationUnitElementInfo unitInfo; + private ImportContainer importContainer; + private final CompilationUnit root; + + public DOMToModelPopulator(Map newElements, CompilationUnit root, CompilationUnitElementInfo unitInfo) { + this.toPopulate = newElements; + this.elements.push(root); + this.infos.push(unitInfo); + this.root = root; + this.unitInfo = unitInfo; + } + + private static void addAsChild(JavaElementInfo parentInfo, IJavaElement childElement) { + if (parentInfo instanceof AnnotatableInfo annotable && childElement instanceof IAnnotation annotation) { + IAnnotation[] newAnnotations = Arrays.copyOf(annotable.annotations, annotable.annotations.length + 1); + newAnnotations[newAnnotations.length - 1] = annotation; + annotable.annotations = newAnnotations; + } else if (parentInfo instanceof OpenableElementInfo openable) { + openable.addChild(childElement); + } else if (parentInfo instanceof SourceTypeElementInfo type) { + type.children = Arrays.copyOf(type.children, type.children.length + 1); + type.children[type.children.length - 1] = childElement; + } else if (parentInfo instanceof ImportContainerInfo importContainer && childElement instanceof org.eclipse.jdt.internal.core.ImportDeclaration importDecl) { + IJavaElement[] newImports = Arrays.copyOf(importContainer.getChildren(), importContainer.getChildren().length + 1); + newImports[newImports.length - 1] = importDecl; + importContainer.children = newImports; + } + } + + @Override + public boolean visit(org.eclipse.jdt.core.dom.CompilationUnit node) { + this.unitInfo.setSourceLength(node.getLength()); + return true; + } + + @Override + public boolean visit(PackageDeclaration node) { + org.eclipse.jdt.internal.core.PackageDeclaration newElement = new org.eclipse.jdt.internal.core.PackageDeclaration(this.root, node.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + AnnotatableInfo newInfo = new AnnotatableInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setNameSourceStart(node.getName().getStartPosition()); + newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(PackageDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } + + @Override + public boolean visit(ImportDeclaration node) { + if (this.importContainer == null) { + this.importContainer = this.root.getImportContainer(); + ImportContainerInfo importContainerInfo = new ImportContainerInfo(); + JavaElementInfo parentInfo = this.infos.peek(); + addAsChild(parentInfo, this.importContainer); + this.toPopulate.put(this.importContainer, importContainerInfo); + } + org.eclipse.jdt.internal.core.ImportDeclaration newElement = new org.eclipse.jdt.internal.core.ImportDeclaration(this.importContainer, node.getName().toString(), node.isOnDemand()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + AnnotatableInfo newInfo = new AnnotatableInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setNameSourceStart(node.getName().getStartPosition()); + newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(ImportDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } + + @Override + public boolean visit(TypeDeclaration node) { + if (node.getAST().apiLevel() > 2) { + ((List)node.typeParameters()) + .stream() + .map(org.eclipse.jdt.core.dom.TypeParameter::getName) + .map(Name::getFullyQualifiedName) + .forEach(this.currentTypeParameters::add); + } + SourceType newElement = new SourceType(this.elements.peek(), node.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setFlags(node.getFlags() | (node.isInterface() ? ClassFileConstants.AccInterface : 0)); + newInfo.setHandle(newElement); + newInfo.setNameSourceStart(node.getName().getStartPosition()); + newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(TypeDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + if (decl.getAST().apiLevel() > 2) { + ((List)decl.typeParameters()) + .stream() + .map(org.eclipse.jdt.core.dom.TypeParameter::getName) + .map(Name::getFullyQualifiedName) + .forEach(this.currentTypeParameters::remove); + } + } + + @Override + public boolean visit(AnnotationTypeDeclaration node) { + SourceType newElement = new SourceType(this.elements.peek(), node.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setFlags(node.getFlags()); + newInfo.setHandle(newElement); + newInfo.setNameSourceStart(node.getName().getStartPosition()); + newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + + @Override + public void endVisit(AnnotationTypeDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } + + @Override + public boolean visit(EnumDeclaration node) { + SourceType newElement = new SourceType(this.elements.peek(), node.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setFlags(node.getFlags()); + newInfo.setHandle(newElement); + newInfo.setNameSourceStart(node.getName().getStartPosition()); + newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(EnumDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } + +// @Override +// public boolean visit(EnumConstantDeclaration node) { +// SourceField newElement = new SourceField(this.elements.peek(), node.getName().toString()); +// this.elements.push(newElement); +// addAsChild(this.infos.peek(), newElement); +// SourceFieldElementInfo info = new SourceFieldElementInfo(); +// info.setSourceRangeStart(node.getStartPosition()); +// info.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); +// info.setFlags(node.getFlags()); +// info.setNameSourceStart(node.getName().getStartPosition()); +// info.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); +// // TODO populate info +// this.infos.push(info); +// this.toPopulate.put(newElement, info); +// return true; +// } +// @Override +// public void endVisit(EnumConstantDeclaration decl) { +// this.elements.pop(); +// this.infos.pop(); +// } + + @Override + public boolean visit(RecordDeclaration node) { + SourceType newElement = new SourceType(this.elements.peek(), node.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setFlags(node.getFlags()); + newInfo.setHandle(newElement); + newInfo.setNameSourceStart(node.getName().getStartPosition()); + newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(RecordDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } + + + @Override + public boolean visit(MethodDeclaration method) { + if (method.getAST().apiLevel() > 2) { + ((List)method.typeParameters()) + .stream() + .map(org.eclipse.jdt.core.dom.TypeParameter::getName) + .map(Name::getFullyQualifiedName) + .forEach(this.currentTypeParameters::add); + } + SourceMethod newElement = new SourceMethod(this.elements.peek(), + method.getName().getIdentifier(), + ((List)method.parameters()).stream() + .map(this::createSignature) + .toArray(String[]::new)); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceMethodInfo info = new SourceMethodInfo(); + info.setArgumentNames(((List)method.parameters()).stream().map(param -> param.getName().toString().toCharArray()).toArray(char[][]::new)); + info.arguments = ((List)method.parameters()).stream() + .map(this::toLocalVariable) + .toArray(LocalVariable[]::new); + if (method.getAST().apiLevel() > 2 && method.getReturnType2() != null) { + info.setReturnType(method.getReturnType2().toString().toCharArray()); + } + info.setSourceRangeStart(method.getStartPosition()); + info.setSourceRangeEnd(method.getStartPosition() + method.getLength() - 1); + info.setFlags(method.getFlags()); + info.setNameSourceStart(method.getName().getStartPosition()); + info.setNameSourceEnd(method.getName().getStartPosition() + method.getName().getLength() - 1); + this.infos.push(info); + this.toPopulate.put(newElement, info); + return true; + } + @Override + public void endVisit(MethodDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + if (decl.getAST().apiLevel() > 2) { + ((List)decl.typeParameters()) + .stream() + .map(org.eclipse.jdt.core.dom.TypeParameter::getName) + .map(Name::getFullyQualifiedName) + .forEach(this.currentTypeParameters::remove); + } + } + + @Override + public boolean visit(AnnotationTypeMemberDeclaration method) { + SourceMethod newElement = new SourceMethod(this.elements.peek(), + method.getName().getIdentifier(), + new String[0]); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceMethodInfo info = new SourceMethodInfo(); + info.setReturnType(method.getType().toString().toCharArray()); + info.setSourceRangeStart(method.getStartPosition()); + info.setSourceRangeEnd(method.getStartPosition() + method.getLength() - 1); + info.setFlags(method.getFlags()); + info.setNameSourceStart(method.getName().getStartPosition()); + info.setNameSourceEnd(method.getName().getStartPosition() + method.getName().getLength() - 1); + this.infos.push(info); + this.toPopulate.put(newElement, info); + return true; + } + @Override + public void endVisit(AnnotationTypeMemberDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } + + @Override + public boolean visit(org.eclipse.jdt.core.dom.TypeParameter typeParam) { + TypeParameter newElement = new TypeParameter(this.elements.peek(), typeParam.getName().getFullyQualifiedName()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + TypeParameterElementInfo info = new TypeParameterElementInfo(); + info.setSourceRangeStart(typeParam.getStartPosition()); + info.setSourceRangeEnd(typeParam.getStartPosition() + typeParam.getLength()); + this.infos.push(info); + return true; + } + @Override + public void endVisit(org.eclipse.jdt.core.dom.TypeParameter typeParam) { + this.elements.pop(); + this.infos.pop(); + } + + @Override + public boolean visit(NormalAnnotation node) { + Annotation newElement = new Annotation(this.elements.peek(), node.getTypeName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + AnnotationInfo newInfo = new AnnotationInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.nameStart = node.getTypeName().getStartPosition(); + newInfo.nameEnd = node.getTypeName().getStartPosition() + node.getTypeName().getLength() - 1; + newInfo.members = ((List)node.values()) + .stream() + .map(domMemberValuePair -> { + Entry value = memberValue(domMemberValuePair.getValue()); + return new org.eclipse.jdt.internal.core.MemberValuePair(domMemberValuePair.getName().toString(), value.getKey(), value.getValue()); + }) + .toArray(IMemberValuePair[]::new); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(NormalAnnotation decl) { + this.elements.pop(); + this.infos.pop(); + } + + @Override + public boolean visit(MarkerAnnotation node) { + Annotation newElement = new Annotation(this.elements.peek(), node.getTypeName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + AnnotationInfo newInfo = new AnnotationInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.members = new IMemberValuePair[0]; + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(MarkerAnnotation decl) { + this.elements.pop(); + this.infos.pop(); + } + + @Override + public boolean visit(SingleMemberAnnotation node) { + Annotation newElement = new Annotation(this.elements.peek(), node.getTypeName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + AnnotationInfo newInfo = new AnnotationInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + Entry value = memberValue(node.getValue()); + newInfo.members = new IMemberValuePair[] { new org.eclipse.jdt.internal.core.MemberValuePair("value", value.getKey(), value.getValue()) }; //$NON-NLS-1$ + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(SingleMemberAnnotation decl) { + this.elements.pop(); + this.infos.pop(); + } + + public Entry memberValue(Expression dom) { + if (dom == null || + dom instanceof NullLiteral nullLiteral || + (dom instanceof SimpleName name && ( + "MISSING".equals(name.getIdentifier()) || //$NON-NLS-1$ // better compare with internal SimpleName.MISSING + Arrays.equals(RecoveryScanner.FAKE_IDENTIFIER, name.getIdentifier().toCharArray())))) { + return new SimpleEntry<>(null, IMemberValuePair.K_UNKNOWN); + } + if (dom instanceof StringLiteral stringValue) { + return new SimpleEntry<>(stringValue.getLiteralValue(), IMemberValuePair.K_STRING); + } + if (dom instanceof BooleanLiteral booleanValue) { + return new SimpleEntry<>(booleanValue.booleanValue(), IMemberValuePair.K_BOOLEAN); + } + if (dom instanceof CharacterLiteral charValue) { + return new SimpleEntry<>(charValue.charValue(), IMemberValuePair.K_CHAR); + } + if (dom instanceof TypeLiteral typeLiteral) { + return new SimpleEntry<>(typeLiteral.getType(), IMemberValuePair.K_CLASS); + } + if (dom instanceof SimpleName simpleName) { + return new SimpleEntry<>(simpleName.toString(), IMemberValuePair.K_SIMPLE_NAME); + } + if (dom instanceof QualifiedName qualifiedName) { + return new SimpleEntry<>(qualifiedName.toString(), IMemberValuePair.K_QUALIFIED_NAME); + } + if (dom instanceof org.eclipse.jdt.core.dom.Annotation annotation) { + return new SimpleEntry<>(toModelAnnotation(annotation), IMemberValuePair.K_ANNOTATION); + } + if (dom instanceof ArrayInitializer arrayInitializer) { + var values = ((List)arrayInitializer.expressions()).stream().map(this::memberValue).toList(); + var types = values.stream().map(Entry::getValue).distinct().toList(); + return new SimpleEntry<>(values.stream().map(Entry::getKey).toArray(), types.size() == 1 ? types.get(0) : IMemberValuePair.K_UNKNOWN); + } + if (dom instanceof NumberLiteral number) { + String token = number.getToken(); + int type = toAnnotationValuePairType(token); + Object value = token; + if ((type == IMemberValuePair.K_LONG && token.endsWith("L")) || //$NON-NLS-1$ + (type == IMemberValuePair.K_FLOAT && token.endsWith("f"))) { //$NON-NLS-1$ + value = token.substring(0, token.length() - 1); + } + if (value instanceof String valueString) { + value = switch (type) { + case IMemberValuePair.K_INT -> Integer.parseInt(valueString); + case IMemberValuePair.K_LONG -> Long.parseLong(valueString); + case IMemberValuePair.K_SHORT -> Short.parseShort(valueString); + case IMemberValuePair.K_BYTE -> Byte.parseByte(valueString); + case IMemberValuePair.K_FLOAT -> Float.parseFloat(valueString); + case IMemberValuePair.K_DOUBLE -> Double.parseDouble(valueString); + default -> throw new IllegalArgumentException("Type not (yet?) supported"); //$NON-NLS-1$ + }; + } + return new SimpleEntry<>(value, type); + } + return new SimpleEntry<>(null, IMemberValuePair.K_UNKNOWN); + } + + private int toAnnotationValuePairType(String token) { + // inspired by NumberLiteral.setToken + Scanner scanner = new Scanner(); + scanner.setSource(token.toCharArray()); + try { + int tokenType = scanner.getNextToken(); + return switch(tokenType) { + case TerminalTokens.TokenNameDoubleLiteral -> IMemberValuePair.K_DOUBLE; + case TerminalTokens.TokenNameIntegerLiteral -> IMemberValuePair.K_INT; + case TerminalTokens.TokenNameFloatingPointLiteral -> IMemberValuePair.K_FLOAT; + case TerminalTokens.TokenNameLongLiteral -> IMemberValuePair.K_LONG; + case TerminalTokens.TokenNameMINUS -> + switch (scanner.getNextToken()) { + case TerminalTokens.TokenNameDoubleLiteral -> IMemberValuePair.K_DOUBLE; + case TerminalTokens.TokenNameIntegerLiteral -> IMemberValuePair.K_INT; + case TerminalTokens.TokenNameFloatingPointLiteral -> IMemberValuePair.K_FLOAT; + case TerminalTokens.TokenNameLongLiteral -> IMemberValuePair.K_LONG; + default -> throw new IllegalArgumentException("Invalid number literal : >" + token + "<"); //$NON-NLS-1$//$NON-NLS-2$ + }; + default -> throw new IllegalArgumentException("Invalid number literal : >" + token + "<"); //$NON-NLS-1$//$NON-NLS-2$ + }; + } catch (InvalidInputException ex) { + ILog.get().error(ex.getMessage(), ex); + return IMemberValuePair.K_UNKNOWN; + } + } + + private Annotation toModelAnnotation(org.eclipse.jdt.core.dom.Annotation domAnnotation) { + IMemberValuePair[] members; + if (domAnnotation instanceof NormalAnnotation normalAnnotation) { + members = ((List)normalAnnotation.values()).stream().map(domMemberValuePair -> { + Entry value = memberValue(domMemberValuePair.getValue()); + return new org.eclipse.jdt.internal.core.MemberValuePair(domMemberValuePair.getName().toString(), value.getKey(), value.getValue()); + }).toArray(IMemberValuePair[]::new); + } else if (domAnnotation instanceof SingleMemberAnnotation single) { + Entry value = memberValue(single.getValue()); + members = new IMemberValuePair[] { new org.eclipse.jdt.internal.core.MemberValuePair("value", value.getKey(), value.getValue())}; //$NON-NLS-1$ + } else { + members = new IMemberValuePair[0]; + } + return new Annotation(null, domAnnotation.getTypeName().toString()) { + @Override + public IMemberValuePair[] getMemberValuePairs() { + return members; + } + }; + } + + private LocalVariable toLocalVariable(SingleVariableDeclaration parameter) { + return new LocalVariable(this.elements.peek(), + parameter.getName().getIdentifier(), + parameter.getStartPosition(), + parameter.getStartPosition() + parameter.getLength(), + parameter.getName().getStartPosition(), + parameter.getName().getStartPosition() + parameter.getName().getLength(), + parameter.getType().toString(), + null, // TODO + parameter.getFlags(), + parameter.getParent() instanceof MethodDeclaration); + } + + @Override + public boolean visit(FieldDeclaration field) { + for (VariableDeclarationFragment fragment : (Collection) field.fragments()) { + SourceField newElement = new SourceField(this.elements.peek(), fragment.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceFieldElementInfo info = new SourceFieldElementInfo(); + info.setTypeName(field.getType().toString().toCharArray()); + info.setSourceRangeStart(field.getStartPosition()); + info.setSourceRangeEnd(field.getStartPosition() + field.getLength() - 1); + info.setFlags(field.getFlags()); + info.setNameSourceStart(fragment.getName().getStartPosition()); + info.setNameSourceEnd(fragment.getName().getStartPosition() + fragment.getName().getLength() - 1); + // TODO populate info + this.infos.push(info); + this.toPopulate.put(newElement, info); + } + return true; + } + @Override + public void endVisit(FieldDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } + + private String createSignature(SingleVariableDeclaration decl) { + String initialSignature = Util.getSignature(decl.getType()); + int extraDimensions = decl.getExtraDimensions(); + if (decl.isVarargs()) { + extraDimensions++; + } + return Signature.createArraySignature(initialSignature, extraDimensions); + } + + @Override + public boolean visit(Initializer node) { + org.eclipse.jdt.internal.core.Initializer newElement = new org.eclipse.jdt.internal.core.Initializer(this.elements.peek(), 1); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + InitializerElementInfo newInfo = new InitializerElementInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setFlags(node.getFlags()); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(Initializer decl) { + this.elements.pop(); + this.infos.pop(); + } + + @Override + public boolean visit(ModuleDeclaration node) { + SourceModule newElement = new SourceModule(this.elements.peek(), node.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + this.unitInfo.setModule(newElement); + try { + this.root.getJavaProject().setModuleDescription(newElement); + } catch (JavaModelException e) { + ILog.get().error(e.getMessage(), e); + } + ModuleDescriptionInfo newInfo = new ModuleDescriptionInfo(); + newInfo.setHandle(newElement); + newInfo.name = node.getName().toString().toCharArray(); + newInfo.setNameSourceStart(node.getName().getStartPosition()); + newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setFlags(node.getFlags()); + List moduleStatements = node.moduleStatements(); + newInfo.requires = moduleStatements.stream() + .filter(RequiresDirective.class::isInstance) + .map(RequiresDirective.class::cast) + .map(this::toModuleReferenceInfo) + .toArray(ModuleReferenceInfo[]::new); + newInfo.exports = moduleStatements.stream() + .filter(ExportsDirective.class::isInstance) + .map(ExportsDirective.class::cast) + .map(req -> toPackageExportInfo(req, req.getName(), req.getFlags())) + .toArray(PackageExportInfo[]::new); + newInfo.opens = moduleStatements.stream() + .filter(OpensDirective.class::isInstance) + .map(OpensDirective.class::cast) + .map(req -> toPackageExportInfo(req, req.getName(), req.getFlags())) + .toArray(PackageExportInfo[]::new); + newInfo.usedServices = moduleStatements.stream() + .filter(UsesDirective.class::isInstance) + .map(UsesDirective.class::cast) + .map(UsesDirective::getName) + .map(Name::toString) + .map(String::toCharArray) + .toArray(char[][]::new); + newInfo.services = moduleStatements.stream() + .filter(ProvidesDirective.class::isInstance) + .map(ProvidesDirective.class::cast) + .map(this::toServiceInfo) + .toArray(ServiceInfo[]::new); + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + return true; + } + @Override + public void endVisit(ModuleDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } + + private ModuleReferenceInfo toModuleReferenceInfo(RequiresDirective node) { + ModuleReferenceInfo res = new ModuleReferenceInfo(); + res.modifiers = node.getModifiers(); + res.name = node.getName().toString().toCharArray(); + res.setSourceRangeStart(node.getStartPosition()); + res.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + return res; + } + private PackageExportInfo toPackageExportInfo(ASTNode node, Name name, int flags) { + PackageExportInfo res = new PackageExportInfo(); + res.flags = flags; + res.pack = name.toString().toCharArray(); + res.setSourceRangeStart(node.getStartPosition()); + res.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + return res; + } + private ServiceInfo toServiceInfo(ProvidesDirective node) { + ServiceInfo res = new ServiceInfo(); + res.flags = node.getFlags(); + res.serviceName = node.getName().toString().toCharArray(); + res.implNames = ((List)node.implementations()).stream().map(Name::toString).map(String::toCharArray).toArray(char[][]::new); + res.setSourceRangeStart(node.getStartPosition()); + res.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + return res; + } +} diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java index 0867ce1c3ab..6b949377eb3 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java @@ -13,6 +13,7 @@ *******************************************************************************/ package org.eclipse.jdt.internal.core; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -25,6 +26,8 @@ import org.eclipse.jdt.core.compiler.CompilationParticipant; import org.eclipse.jdt.core.compiler.ReconcileContext; import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.core.util.Messages; import org.eclipse.jdt.internal.core.util.Util; @@ -175,7 +178,6 @@ public org.eclipse.jdt.core.dom.CompilationUnit makeConsistent(CompilationUnit w if (this.ast != null) return this.ast; // no need to recompute AST if known already - CompilationUnitDeclaration unit = null; try { JavaModelManager.getJavaModelManager().abortOnMissingSource.set(Boolean.TRUE); CompilationUnit source = workingCopy.cloneCachingContents(); @@ -183,33 +185,60 @@ public org.eclipse.jdt.core.dom.CompilationUnit makeConsistent(CompilationUnit w if (JavaProject.hasJavaNature(workingCopy.getJavaProject().getProject()) && (this.reconcileFlags & ICompilationUnit.FORCE_PROBLEM_DETECTION) != 0) { this.resolveBindings = this.requestorIsActive; - if (this.problems == null) + if (this.problems == null) { this.problems = new HashMap<>(); - unit = - CompilationUnitProblemFinder.process( - source, - this.workingCopyOwner, - this.problems, - this.astLevel != ICompilationUnit.NO_AST/*creating AST if level is not NO_AST */, - this.reconcileFlags, - this.progressMonitor); - if (this.progressMonitor != null) this.progressMonitor.worked(1); - } - - // create AST if needed - if (this.astLevel != ICompilationUnit.NO_AST - && unit !=null/*unit is null if working copy is consistent && (problem detection not forced || non-Java project) -> don't create AST as per API*/) { + } Map options = workingCopy.getJavaProject().getOptions(true); - // convert AST - this.ast = - AST.convertCompilationUnit( - this.astLevel, - unit, - options, - this.resolveBindings, - source, - this.reconcileFlags, - this.progressMonitor); + if (CompilationUnit.DOM_BASED_OPERATIONS) { + ASTParser parser = ASTParser.newParser(this.astLevel >= 0 ? this.astLevel : AST.getJLSLatest()); + parser.setResolveBindings(this.resolveBindings || (this.reconcileFlags & ICompilationUnit.FORCE_PROBLEM_DETECTION) != 0); + parser.setCompilerOptions(options); + parser.setSource(source); + org.eclipse.jdt.core.dom.CompilationUnit newAST = (org.eclipse.jdt.core.dom.CompilationUnit) parser.createAST(this.progressMonitor); + if ((this.reconcileFlags & ICompilationUnit.FORCE_PROBLEM_DETECTION) != 0 && newAST != null) { + newAST.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString()); //trigger resolution and analysis + } + CategorizedProblem[] newProblems = Arrays.stream(newAST.getProblems()) + .filter(CategorizedProblem.class::isInstance) + .map(CategorizedProblem.class::cast) + .toArray(CategorizedProblem[]::new); + this.problems.put(Integer.toString(CategorizedProblem.CAT_UNSPECIFIED), newProblems); // TODO better category + if (this.astLevel != ICompilationUnit.NO_AST) { + this.ast = newAST; + } + } else { + CompilationUnitDeclaration unit = null; + try { + unit = CompilationUnitProblemFinder.process( + source, + this.workingCopyOwner, + this.problems, + this.astLevel != ICompilationUnit.NO_AST/*creating AST if level is not NO_AST */, + this.reconcileFlags, + this.progressMonitor); + if (this.progressMonitor != null) this.progressMonitor.worked(1); + + // create AST if needed + if (this.astLevel != ICompilationUnit.NO_AST + && unit !=null/*unit is null if working copy is consistent && (problem detection not forced || non-Java project) -> don't create AST as per API*/) { + // convert AST + this.ast = + AST.convertCompilationUnit( + this.astLevel, + unit, + options, + this.resolveBindings, + source, + this.reconcileFlags, + this.progressMonitor); + } + } finally { + if (unit != null) { + unit.cleanUp(); + } + } + } + if (this.ast != null) { if (this.deltaBuilder.delta == null) { this.deltaBuilder.delta = new JavaElementDelta(workingCopy); @@ -225,9 +254,6 @@ public org.eclipse.jdt.core.dom.CompilationUnit makeConsistent(CompilationUnit w // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=100919) } finally { JavaModelManager.getJavaModelManager().abortOnMissingSource.remove(); - if (unit != null) { - unit.cleanUp(); - } } return this.ast; } diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/JavaSearchDocument.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/JavaSearchDocument.java index 682db632773..78d98fa94cc 100644 --- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/JavaSearchDocument.java +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/JavaSearchDocument.java @@ -87,7 +87,7 @@ public String getEncoding() { } return null; } - private IFile getFile() { + public IFile getFile() { if (this.file == null) this.file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(getPath())); return this.file; diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/DOMToIndexVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/DOMToIndexVisitor.java new file mode 100644 index 00000000000..44ad4760d2f --- /dev/null +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/DOMToIndexVisitor.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.core.search.indexing; + +import java.util.List; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.core.dom.RecordDeclaration; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; + +class DOMToIndexVisitor extends ASTVisitor { + + private SourceIndexer sourceIndexer; + + public DOMToIndexVisitor(SourceIndexer sourceIndexer) { + this.sourceIndexer = sourceIndexer; + } + + @Override + public boolean visit(TypeDeclaration type) { + if (type.isInterface()) { + this.sourceIndexer.addInterfaceDeclaration(type.getModifiers(), getPackage(type), type.getName().toString().toCharArray(), null, ((List)type.superInterfaceTypes()).stream().map(superInterface -> superInterface.toString().toCharArray()).toArray(char[][]::new), null, false); + } else { + this.sourceIndexer.addClassDeclaration(type.getModifiers(), getPackage(type), type.getName().toString().toCharArray(), null, type.getSuperclassType() == null ? null : type.getSuperclassType().toString().toCharArray(), + ((List)type.superInterfaceTypes()).stream().map(superInterface -> superInterface.toString().toCharArray()).toArray(char[][]::new), null, isSecondary(type)); + } + // TODO other types + return true; + } + + private boolean isSecondary(TypeDeclaration type) { + return type.getParent() instanceof CompilationUnit unit && + unit.types().size() > 1 && + unit.types().indexOf(type) > 0; + // TODO: check name? + } + + private char[] getPackage(ASTNode node) { + while (node != null && !(node instanceof CompilationUnit)) { + node = node.getParent(); + } + return node == null ? null : + node instanceof CompilationUnit unit && unit.getPackage() != null ? unit.getPackage().getName().toString().toCharArray() : + null; + } + + @Override + public boolean visit(RecordDeclaration recordDecl) { + // copied processing of TypeDeclaration + this.sourceIndexer.addClassDeclaration(recordDecl.getModifiers(), getPackage(recordDecl), recordDecl.getName().toString().toCharArray(), null, null, + ((List)recordDecl.superInterfaceTypes()).stream().map(type -> type.toString().toCharArray()).toArray(char[][]::new), null, false); + return true; + } + + @Override + public boolean visit(MethodDeclaration method) { + char[] methodName = method.getName().toString().toCharArray(); + char[][] parameterTypes = ((List)method.parameters()).stream() + .filter(SingleVariableDeclaration.class::isInstance) + .map(SingleVariableDeclaration.class::cast) + .map(SingleVariableDeclaration::getType) + .map(Type::toString) + .map(String::toCharArray) + .toArray(char[][]::new); + char[] returnType = null; + if (method.getReturnType2() instanceof SimpleType simple) { + returnType = simple.getName().toString().toCharArray(); + } else if (method.getReturnType2() instanceof PrimitiveType primitive) { + returnType = primitive.getPrimitiveTypeCode().toString().toCharArray(); + } else if (method.getReturnType2() == null) { + // do nothing + } else { + returnType = method.getReturnType2().toString().toCharArray(); + } + char[][] exceptionTypes = ((List)method.thrownExceptionTypes()).stream() + .map(Type::toString) + .map(String::toCharArray) + .toArray(char[][]::new); + this.sourceIndexer.addMethodDeclaration(methodName, parameterTypes, returnType, exceptionTypes); + char[][] parameterNames = ((List)method.parameters()).stream() + .map(VariableDeclaration::getName) + .map(SimpleName::toString) + .map(String::toCharArray) + .toArray(char[][]::new); + this.sourceIndexer.addMethodDeclaration(null, + null /* TODO: fully qualified name of enclosing type? */, + methodName, + parameterTypes.length, + null, + parameterTypes, + parameterNames, + returnType, + method.getModifiers(), + getPackage(method), + 0 /* TODO What to put here? */, + exceptionTypes, + 0 /* TODO ExtraFlags.IsLocalType ? */); + return true; + } + + @Override + public boolean visit(FieldDeclaration field) { + char[] typeName = field.getType().toString().toCharArray(); + for (VariableDeclarationFragment fragment: (List)field.fragments()) { + this.sourceIndexer.addFieldDeclaration(typeName, fragment.getName().toString().toCharArray()); + } + return true; + } + + // TODO (cf SourceIndexer and SourceIndexerRequestor) + // * Module: addModuleDeclaration/addModuleReference/addModuleExportedPackages + // * Lambda: addIndexEntry/addClassDeclaration + // * addMethodReference + // * addConstructorReference +} diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java index 5f5548c6009..1c3cede84b3 100644 --- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java @@ -15,13 +15,20 @@ import static org.eclipse.jdt.internal.core.JavaModelManager.trace; +import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.search.SearchDocument; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; @@ -48,12 +55,14 @@ import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; +import org.eclipse.jdt.internal.core.ASTHolderCUInfo; import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; import org.eclipse.jdt.internal.core.JavaModel; import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.core.SourceTypeElementInfo; import org.eclipse.jdt.internal.core.jdom.CompilationUnit; +import org.eclipse.jdt.internal.core.search.JavaSearchDocument; import org.eclipse.jdt.internal.core.search.matching.JavaSearchNameEnvironment; import org.eclipse.jdt.internal.core.search.matching.MethodPattern; import org.eclipse.jdt.internal.core.search.processing.JobManager; @@ -88,6 +97,10 @@ public SourceIndexer(SearchDocument document) { } @Override public void indexDocument() { + if (Boolean.getBoolean(getClass().getSimpleName() + ".DOM_BASED_INDEXER")) { //$NON-NLS-1$ + indexDocumentFromDOM(); + return; + } // Create a new Parser String documentPath = this.document.getPath(); SourceElementParser parser = this.document.getParser(); @@ -271,4 +284,37 @@ public void indexResolvedDocument() { } } } + + /** + * @return whether the operatin was successful + */ + private boolean indexDocumentFromDOM() { + if (this.document instanceof JavaSearchDocument javaSearchDoc) { + IFile file = javaSearchDoc.getFile(); + try { + if (JavaProject.hasJavaNature(file.getProject())) { + IJavaProject javaProject = JavaCore.create(file.getProject()); + IClasspathEntry cpEntry = javaProject.findContainingClasspathEntry(file); + IJavaElement element = javaProject.findElement(file.getFullPath().makeRelativeTo(cpEntry.getPath())); + if (element instanceof org.eclipse.jdt.internal.core.CompilationUnit modelUnit) { + // TODO check element info: if has AST and flags are set sufficiently, just reuse instead of rebuilding + ASTParser astParser = ASTParser.newParser(modelUnit.getElementInfo() instanceof ASTHolderCUInfo astHolder ? astHolder.astLevel : AST.getJLSLatest()); + astParser.setSource(modelUnit); + astParser.setResolveBindings(false); + astParser.setProject(javaProject); + astParser.setIgnoreMethodBodies(true); + org.eclipse.jdt.core.dom.ASTNode dom = astParser.createAST(null); + if (dom != null) { + dom.accept(new DOMToIndexVisitor(this)); + return true; + } + } + } + } catch (Exception ex) { + ILog.get().error("Failed to index document from DOM for " + this.document.getPath(), ex); //$NON-NLS-1$ + } + } + ILog.get().warn("Could not convert DOM to Index for " + this.document.getPath()); //$NON-NLS-1$ + return false; + } } From e2555a1b8e2ccf65a433ce41a8258451b2b57c97 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 7 Feb 2024 17:10:40 -0500 Subject: [PATCH 03/26] Set methods with null return type to have "void" return type Addresses NPE in JavaSearchTests.testEnum04 Signed-off-by: David Thompson --- .../eclipse/jdt/internal/core/DOMToModelPopulator.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 215f1c1fe3e..71ca9949275 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -311,8 +311,12 @@ public boolean visit(MethodDeclaration method) { info.arguments = ((List)method.parameters()).stream() .map(this::toLocalVariable) .toArray(LocalVariable[]::new); - if (method.getAST().apiLevel() > 2 && method.getReturnType2() != null) { - info.setReturnType(method.getReturnType2().toString().toCharArray()); + if (method.getAST().apiLevel() > 2) { + if (method.getReturnType2() != null) { + info.setReturnType(method.getReturnType2().toString().toCharArray()); + } else { + info.setReturnType("void".toCharArray()); //$NON-NLS-1$ + } } info.setSourceRangeStart(method.getStartPosition()); info.setSourceRangeEnd(method.getStartPosition() + method.getLength() - 1); From ddbb87161da8d72c90c103dab7863b0d76e58a98 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Fri, 9 Feb 2024 11:42:36 +0100 Subject: [PATCH 04/26] Add specific GH workflow for DOM-first and Javac Adds some steps on the relevant branches, so the necessary system properties are set in tests to enable DOM-first and Javac. --- .github/workflows/ci-dom-javac.yml | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/ci-dom-javac.yml diff --git a/.github/workflows/ci-dom-javac.yml b/.github/workflows/ci-dom-javac.yml new file mode 100644 index 00000000000..f882a43a0cf --- /dev/null +++ b/.github/workflows/ci-dom-javac.yml @@ -0,0 +1,58 @@ +name: Continuous Integration with DOM/Javac +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-dom + cancel-in-progress: true + +on: + push: + branches: [ 'dom-based-operations', 'dom-with-javac' ] + pull_request: + branches: [ 'dom-based-operations', 'dom-with-javac' ] + +jobs: + event_file: + name: "Upload Event File" + runs-on: ubuntu-latest + steps: + - name: Upload + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: Event File + path: ${{ github.event_path }} + build-dom-javac: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + - name: Enable DOM-first and Javac + run: sed -i 's$$ -DCompilationUnit.DOM_BASED_OPERATIONS=true -DSourceIndexer.DOM_BASED_INDEXER=false -DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver -DAbstractImageBuilder.compiler=org.eclipse.jdt.internal.javac.JavacCompiler_$g' */pom.xml + - name: Set up JDKs ☕ + uses: actions/setup-java@v3 + with: + java-version: | + 8 + 17 + 20 + mvn-toolchain-id: | + JavaSE-1.8 + JavaSE-17 + JavaSE-20 + distribution: 'temurin' + - name: Set up Maven + uses: stCarolas/setup-maven@07fbbe97d97ef44336b7382563d66743297e442f # v4.5 + with: + maven-version: 3.9.3 + - name: Build with Maven 🏗️ + run: | + mvn clean install --batch-mode -f org.eclipse.jdt.core.compiler.batch -DlocalEcjVersion=99.99 + mvn -U clean verify --batch-mode --fail-at-end -Ptest-on-javase-20 -Pbree-libs -Papi-check -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 -Dtycho.surefire.argLine="--add-modules ALL-SYSTEM -Dcompliance=1.8,11,17,20 -Djdt.performance.asserts=disabled" -Dcbi-ecj-version=99.99 + - name: Upload Test Results for Linux + if: always() + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: test-results-linux-dom-javac + if-no-files-found: warn + path: | + ${{ github.workspace }}/**/target/surefire-reports/*.xml From 0a45d93640a957ed7ad2eb898bf13aa0ed136c54 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Fri, 9 Feb 2024 16:21:44 -0500 Subject: [PATCH 05/26] [DOM to Model] Support default value, deprecated, and more [DOM to Model] Support default value, deprecated, and more - Use modifiers instead of flags to populate `MemberElementInfo.flags` (the field and setter are misnamed, it contains the modifiers) - Call code that searches for @Deprecated annotation on all body declarations - Cache calls to check for an imported class called `Deprecated` so that it doesn't happen multiple times Signed-off-by: David Thompson --- .../internal/core/DOMToModelPopulator.java | 102 +++++++++++++++--- 1 file changed, 88 insertions(+), 14 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 71ca9949275..873d51d6431 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -10,18 +10,19 @@ *******************************************************************************/ package org.eclipse.jdt.internal.core; +import java.util.AbstractMap.SimpleEntry; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.Stack; -import java.util.AbstractMap.SimpleEntry; -import java.util.Map.Entry; import org.eclipse.core.runtime.ILog; import org.eclipse.jdt.core.IAnnotation; +import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.JavaModelException; @@ -32,6 +33,7 @@ import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.CharacterLiteral; import org.eclipse.jdt.core.dom.EnumDeclaration; @@ -50,6 +52,7 @@ import org.eclipse.jdt.core.dom.NumberLiteral; import org.eclipse.jdt.core.dom.OpensDirective; import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.PrefixExpression; import org.eclipse.jdt.core.dom.ProvidesDirective; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.RecordDeclaration; @@ -85,6 +88,7 @@ class DOMToModelPopulator extends ASTVisitor { private final CompilationUnitElementInfo unitInfo; private ImportContainer importContainer; private final CompilationUnit root; + private Boolean alternativeDeprecated = null; public DOMToModelPopulator(Map newElements, CompilationUnit root, CompilationUnitElementInfo unitInfo) { this.toPopulate = newElements; @@ -177,9 +181,10 @@ public boolean visit(TypeDeclaration node) { this.elements.push(newElement); addAsChild(this.infos.peek(), newElement); SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); + boolean isDeprecated = isNodeDeprecated(node); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); - newInfo.setFlags(node.getFlags() | (node.isInterface() ? ClassFileConstants.AccInterface : 0)); + newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); @@ -208,7 +213,8 @@ public boolean visit(AnnotationTypeDeclaration node) { SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); - newInfo.setFlags(node.getFlags()); + boolean isDeprecated = isNodeDeprecated(node); + newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); @@ -231,7 +237,8 @@ public boolean visit(EnumDeclaration node) { SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); - newInfo.setFlags(node.getFlags()); + boolean isDeprecated = isNodeDeprecated(node); + newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); @@ -275,7 +282,8 @@ public boolean visit(RecordDeclaration node) { SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); - newInfo.setFlags(node.getFlags()); + boolean isDeprecated = isNodeDeprecated(node); + newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); @@ -320,7 +328,8 @@ public boolean visit(MethodDeclaration method) { } info.setSourceRangeStart(method.getStartPosition()); info.setSourceRangeEnd(method.getStartPosition() + method.getLength() - 1); - info.setFlags(method.getFlags()); + boolean isDeprecated = isNodeDeprecated(method); + info.setFlags(method.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); info.setNameSourceStart(method.getName().getStartPosition()); info.setNameSourceEnd(method.getName().getStartPosition() + method.getName().getLength() - 1); this.infos.push(info); @@ -347,13 +356,22 @@ public boolean visit(AnnotationTypeMemberDeclaration method) { new String[0]); this.elements.push(newElement); addAsChild(this.infos.peek(), newElement); - SourceMethodInfo info = new SourceMethodInfo(); + SourceAnnotationMethodInfo info = new SourceAnnotationMethodInfo(); info.setReturnType(method.getType().toString().toCharArray()); info.setSourceRangeStart(method.getStartPosition()); info.setSourceRangeEnd(method.getStartPosition() + method.getLength() - 1); - info.setFlags(method.getFlags()); + boolean isDeprecated = isNodeDeprecated(method); + info.setFlags(method.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); info.setNameSourceStart(method.getName().getStartPosition()); info.setNameSourceEnd(method.getName().getStartPosition() + method.getName().getLength() - 1); + Expression defaultExpr = method.getDefault(); + if (defaultExpr != null) { + Entry value = memberValue(defaultExpr); + org.eclipse.jdt.internal.core.MemberValuePair mvp = new org.eclipse.jdt.internal.core.MemberValuePair(newElement.getElementName(), value.getKey(), value.getValue()); + info.defaultValue = mvp; + info.defaultValueStart = defaultExpr.getStartPosition(); + info.defaultValueEnd = defaultExpr.getStartPosition() + defaultExpr.getLength(); + } this.infos.push(info); this.toPopulate.put(newElement, info); return true; @@ -502,6 +520,14 @@ public Entry memberValue(Expression dom) { } return new SimpleEntry<>(value, type); } + if (dom instanceof PrefixExpression prefixExpression) { + Expression operand = prefixExpression.getOperand(); + if (!(operand instanceof NumberLiteral) && !(operand instanceof BooleanLiteral)) { + return new SimpleEntry<>(null, IMemberValuePair.K_UNKNOWN); + } + Entry entry = memberValue(prefixExpression.getOperand()); + return new SimpleEntry<>(prefixExpression.getOperator().toString() + entry.getKey(), entry.getValue()); + } return new SimpleEntry<>(null, IMemberValuePair.K_UNKNOWN); } @@ -568,15 +594,17 @@ private LocalVariable toLocalVariable(SingleVariableDeclaration parameter) { @Override public boolean visit(FieldDeclaration field) { + JavaElementInfo parent = this.infos.peek(); + boolean isDeprecated = isNodeDeprecated(field); for (VariableDeclarationFragment fragment : (Collection) field.fragments()) { SourceField newElement = new SourceField(this.elements.peek(), fragment.getName().toString()); this.elements.push(newElement); - addAsChild(this.infos.peek(), newElement); + addAsChild(parent, newElement); SourceFieldElementInfo info = new SourceFieldElementInfo(); info.setTypeName(field.getType().toString().toCharArray()); info.setSourceRangeStart(field.getStartPosition()); info.setSourceRangeEnd(field.getStartPosition() + field.getLength() - 1); - info.setFlags(field.getFlags()); + info.setFlags(field.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); info.setNameSourceStart(fragment.getName().getStartPosition()); info.setNameSourceEnd(fragment.getName().getStartPosition() + fragment.getName().getLength() - 1); // TODO populate info @@ -587,8 +615,11 @@ public boolean visit(FieldDeclaration field) { } @Override public void endVisit(FieldDeclaration decl) { - this.elements.pop(); - this.infos.pop(); + int numFragments = decl.fragments().size(); + for (int i = 0; i < numFragments; i++) { + this.elements.pop(); + this.infos.pop(); + } } private String createSignature(SingleVariableDeclaration decl) { @@ -608,7 +639,7 @@ public boolean visit(Initializer node) { InitializerElementInfo newInfo = new InitializerElementInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); - newInfo.setFlags(node.getFlags()); + newInfo.setFlags(node.getModifiers()); this.infos.push(newInfo); this.toPopulate.put(newElement, newInfo); return true; @@ -701,4 +732,47 @@ private ServiceInfo toServiceInfo(ProvidesDirective node) { res.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); return res; } + private boolean isNodeDeprecated(BodyDeclaration node) { + return ((List)node.modifiers()).stream() // + .anyMatch(modifier -> { + if (!isAnnotation(modifier)) { + return false; + } + String potentiallyUnqualifiedAnnotationType = ((org.eclipse.jdt.core.dom.Annotation)modifier).getTypeName().toString(); + if ("java.lang.Deprecated".equals(potentiallyUnqualifiedAnnotationType)) { //$NON-NLS-1$ + return true; + } + return "Deprecated".equals(potentiallyUnqualifiedAnnotationType) && !hasAlternativeDeprecated(); //$NON-NLS-1$ + }); + } + private static boolean isAnnotation(ASTNode node) { + int nodeType = node.getNodeType(); + return nodeType == ASTNode.MARKER_ANNOTATION || nodeType == ASTNode.SINGLE_MEMBER_ANNOTATION + || nodeType == ASTNode.NORMAL_ANNOTATION; + } + private boolean hasAlternativeDeprecated() { + if (this.alternativeDeprecated != null) { + return this.alternativeDeprecated; + } + try { + IJavaElement[] importElements = this.importContainer.getChildren(); + for (IJavaElement child : importElements) { + IImportDeclaration importDeclaration = (IImportDeclaration) child; + // It's possible that the user has imported + // an annotation called "Deprecated" using a wildcard import + // that replaces "java.lang.Deprecated" + // However, it's very costly and complex to check if they've done this, + // so I haven't bothered. + if (!importDeclaration.isOnDemand() + && importDeclaration.getElementName().endsWith("Deprecated")) { //$NON-NLS-1$ + this.alternativeDeprecated = true; + return this.alternativeDeprecated; + } + } + } catch (JavaModelException e) { + // do nothing + } + this.alternativeDeprecated = false; + return this.alternativeDeprecated; + } } From 5ef3b467cb350e537e0327df237dc3347efc7c66 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Sat, 10 Feb 2024 01:06:08 +0100 Subject: [PATCH 06/26] Test execution report --- .github/workflows/ci-dom-javac.yml | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-dom-javac.yml b/.github/workflows/ci-dom-javac.yml index f882a43a0cf..cff970c4659 100644 --- a/.github/workflows/ci-dom-javac.yml +++ b/.github/workflows/ci-dom-javac.yml @@ -10,18 +10,14 @@ on: branches: [ 'dom-based-operations', 'dom-with-javac' ] jobs: - event_file: - name: "Upload Event File" - runs-on: ubuntu-latest - steps: - - name: Upload - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 - with: - name: Event File - path: ${{ github.event_path }} build-dom-javac: runs-on: ubuntu-latest steps: + - name: Install xmllint + shell: bash + run: | + sudo apt update + sudo apt install -y libxml2-utils - uses: actions/checkout@v3 with: submodules: recursive @@ -48,11 +44,10 @@ jobs: run: | mvn clean install --batch-mode -f org.eclipse.jdt.core.compiler.batch -DlocalEcjVersion=99.99 mvn -U clean verify --batch-mode --fail-at-end -Ptest-on-javase-20 -Pbree-libs -Papi-check -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 -Dtycho.surefire.argLine="--add-modules ALL-SYSTEM -Dcompliance=1.8,11,17,20 -Djdt.performance.asserts=disabled" -Dcbi-ecj-version=99.99 - - name: Upload Test Results for Linux - if: always() - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 - with: - name: test-results-linux-dom-javac - if-no-files-found: warn - path: | - ${{ github.workspace }}/**/target/surefire-reports/*.xml + - name: Test Report + if: success() || failure() # run this step even if previous step failed + run: | + echo ▶️ TESTS RUN: $(xmllint --xpath 'string(/testsuite/@tests)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + echo ❌ FAILURES: $(xmllint --xpath 'string(/testsuite/@failures)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + echo 💥 ERRORS: $(xmllint --xpath 'string(/testsuite/@errors)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + echo 🛑 SKIPPED: $(xmllint --xpath 'string(/testsuite/@skipped)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) \ No newline at end of file From 52e7ecc3ec7c96fadcc7a2f84757c3976473cdd3 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Mon, 12 Feb 2024 11:28:30 +0100 Subject: [PATCH 07/26] Fix NPE with importContainer --- .../internal/core/DOMToModelPopulator.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 873d51d6431..56f011e0cec 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -754,23 +754,25 @@ private boolean hasAlternativeDeprecated() { if (this.alternativeDeprecated != null) { return this.alternativeDeprecated; } - try { - IJavaElement[] importElements = this.importContainer.getChildren(); - for (IJavaElement child : importElements) { - IImportDeclaration importDeclaration = (IImportDeclaration) child; - // It's possible that the user has imported - // an annotation called "Deprecated" using a wildcard import - // that replaces "java.lang.Deprecated" - // However, it's very costly and complex to check if they've done this, - // so I haven't bothered. - if (!importDeclaration.isOnDemand() - && importDeclaration.getElementName().endsWith("Deprecated")) { //$NON-NLS-1$ - this.alternativeDeprecated = true; - return this.alternativeDeprecated; + if (this.importContainer != null) { + try { + IJavaElement[] importElements = this.importContainer.getChildren(); + for (IJavaElement child : importElements) { + IImportDeclaration importDeclaration = (IImportDeclaration) child; + // It's possible that the user has imported + // an annotation called "Deprecated" using a wildcard import + // that replaces "java.lang.Deprecated" + // However, it's very costly and complex to check if they've done this, + // so I haven't bothered. + if (!importDeclaration.isOnDemand() + && importDeclaration.getElementName().endsWith("Deprecated")) { //$NON-NLS-1$ + this.alternativeDeprecated = true; + return this.alternativeDeprecated; + } } + } catch (JavaModelException e) { + // do nothing } - } catch (JavaModelException e) { - // do nothing } this.alternativeDeprecated = false; return this.alternativeDeprecated; From 975aca26336ca66d88fe6c742abe44e33e94d3e6 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Mon, 12 Feb 2024 15:29:02 +0100 Subject: [PATCH 08/26] DOM to Model: better support for TypeParameters --- .../internal/core/DOMToModelPopulator.java | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 56f011e0cec..64cbb6e04ad 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -61,6 +61,7 @@ import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.UsesDirective; @@ -103,15 +104,35 @@ private static void addAsChild(JavaElementInfo parentInfo, IJavaElement childEle IAnnotation[] newAnnotations = Arrays.copyOf(annotable.annotations, annotable.annotations.length + 1); newAnnotations[newAnnotations.length - 1] = annotation; annotable.annotations = newAnnotations; - } else if (parentInfo instanceof OpenableElementInfo openable) { - openable.addChild(childElement); - } else if (parentInfo instanceof SourceTypeElementInfo type) { - type.children = Arrays.copyOf(type.children, type.children.length + 1); - type.children[type.children.length - 1] = childElement; - } else if (parentInfo instanceof ImportContainerInfo importContainer && childElement instanceof org.eclipse.jdt.internal.core.ImportDeclaration importDecl) { + return; + } + if (childElement instanceof TypeParameter typeParam) { + if (parentInfo instanceof SourceTypeElementInfo type) { + type.typeParameters = Arrays.copyOf(type.typeParameters, type.typeParameters.length + 1); + type.typeParameters[type.typeParameters.length - 1] = typeParam; + return; + } + if (parentInfo instanceof SourceMethodElementInfo method) { + method.typeParameters = Arrays.copyOf(method.typeParameters, method.typeParameters.length + 1); + method.typeParameters[method.typeParameters.length - 1] = typeParam; + return; + } + } + if (parentInfo instanceof ImportContainerInfo importContainer && childElement instanceof org.eclipse.jdt.internal.core.ImportDeclaration importDecl) { IJavaElement[] newImports = Arrays.copyOf(importContainer.getChildren(), importContainer.getChildren().length + 1); newImports[newImports.length - 1] = importDecl; importContainer.children = newImports; + return; + } + // if nothing more specialized, add as child + if (parentInfo instanceof SourceTypeElementInfo type) { + type.children = Arrays.copyOf(type.children, type.children.length + 1); + type.children[type.children.length - 1] = childElement; + return; + } + if (parentInfo instanceof OpenableElementInfo openable) { + openable.addChild(childElement); + return; } } @@ -383,14 +404,19 @@ public void endVisit(AnnotationTypeMemberDeclaration decl) { } @Override - public boolean visit(org.eclipse.jdt.core.dom.TypeParameter typeParam) { - TypeParameter newElement = new TypeParameter(this.elements.peek(), typeParam.getName().getFullyQualifiedName()); + public boolean visit(org.eclipse.jdt.core.dom.TypeParameter node) { + TypeParameter newElement = new TypeParameter(this.elements.peek(), node.getName().getFullyQualifiedName()); this.elements.push(newElement); addAsChild(this.infos.peek(), newElement); TypeParameterElementInfo info = new TypeParameterElementInfo(); - info.setSourceRangeStart(typeParam.getStartPosition()); - info.setSourceRangeEnd(typeParam.getStartPosition() + typeParam.getLength()); + info.setSourceRangeStart(node.getStartPosition()); + info.setSourceRangeEnd(node.getStartPosition() + node.getLength()); + info.nameStart = node.getName().getStartPosition(); + info.nameEnd = node.getName().getStartPosition() + node.getName().getLength() - 1; + info.bounds = ((List)node.typeBounds()).stream().map(Type::toString).map(String::toCharArray).toArray(char[][]::new); + info.boundsSignatures = ((List)node.typeBounds()).stream().map(Type::toString).map(String::toCharArray).toArray(char[][]::new); this.infos.push(info); + this.toPopulate.put(newElement, info); return true; } @Override From 990744a39887416bce48f77f60d53dcf199b0209 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 12 Feb 2024 14:30:02 -0500 Subject: [PATCH 09/26] Fixes to `DOMToModelPopulator` to address test failures in `CompilationUnitTests` (#21) * Re-enable populating Enum constants Signed-off-by: David Thompson --- .../jdt/internal/core/CompilationUnit.java | 6 +- .../internal/core/DOMToModelPopulator.java | 176 ++++++++++++++---- 2 files changed, 144 insertions(+), 38 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index 13b3d597ecf..4f5cfb011de 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -178,7 +178,11 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito newAST.accept(new DOMToModelPopulator(newElements, this, unitInfo)); // unitInfo.setModule(); // unitInfo.setSourceLength(newSourceLength); - unitInfo.setIsStructureKnown(true); + boolean structureKnown = true; + for (IProblem problem : newAST.getProblems()) { + structureKnown &= (IProblem.Syntax & problem.getID()) == 0; + } + unitInfo.setIsStructureKnown(structureKnown); } } else { CompilationUnitStructureRequestor requestor = new CompilationUnitStructureRequestor(this, unitInfo, newElements); diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 64cbb6e04ad..0173d318a87 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -19,6 +19,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.Stack; +import java.util.stream.Stream; import org.eclipse.core.runtime.ILog; import org.eclipse.jdt.core.IAnnotation; @@ -30,12 +31,14 @@ import org.eclipse.jdt.core.compiler.InvalidInputException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AbstractTagElement; import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.ExportsDirective; import org.eclipse.jdt.core.dom.Expression; @@ -62,6 +65,7 @@ import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TagElement; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.UsesDirective; @@ -88,6 +92,7 @@ class DOMToModelPopulator extends ASTVisitor { private final Set currentTypeParameters = new HashSet<>(); private final CompilationUnitElementInfo unitInfo; private ImportContainer importContainer; + private ImportContainerInfo importContainerInfo; private final CompilationUnit root; private Boolean alternativeDeprecated = null; @@ -166,15 +171,15 @@ public void endVisit(PackageDeclaration decl) { public boolean visit(ImportDeclaration node) { if (this.importContainer == null) { this.importContainer = this.root.getImportContainer(); - ImportContainerInfo importContainerInfo = new ImportContainerInfo(); + this.importContainerInfo = new ImportContainerInfo(); JavaElementInfo parentInfo = this.infos.peek(); addAsChild(parentInfo, this.importContainer); - this.toPopulate.put(this.importContainer, importContainerInfo); + this.toPopulate.put(this.importContainer, this.importContainerInfo); } org.eclipse.jdt.internal.core.ImportDeclaration newElement = new org.eclipse.jdt.internal.core.ImportDeclaration(this.importContainer, node.getName().toString(), node.isOnDemand()); this.elements.push(newElement); - addAsChild(this.infos.peek(), newElement); - AnnotatableInfo newInfo = new AnnotatableInfo(); + addAsChild(this.importContainerInfo, newElement); + ImportDeclarationElementInfo newInfo = new ImportDeclarationElementInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); newInfo.setNameSourceStart(node.getName().getStartPosition()); @@ -203,6 +208,17 @@ public boolean visit(TypeDeclaration node) { addAsChild(this.infos.peek(), newElement); SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); boolean isDeprecated = isNodeDeprecated(node); + char[][] categories = getCategories(node); + newInfo.addCategories(newElement, categories); + JavaElementInfo toPopulateCategories = this.infos.peek(); + while (toPopulateCategories != null) { + if (toPopulateCategories instanceof SourceTypeElementInfo parentTypeInfo) { + parentTypeInfo.addCategories(newElement, categories); + toPopulateCategories = (JavaElementInfo)parentTypeInfo.getEnclosingType(); + } else { + break; + } + } newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); @@ -234,6 +250,17 @@ public boolean visit(AnnotationTypeDeclaration node) { SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + char[][] categories = getCategories(node); + newInfo.addCategories(newElement, categories); + JavaElementInfo toPopulateCategories = this.infos.peek(); + while (toPopulateCategories != null) { + if (toPopulateCategories instanceof SourceTypeElementInfo parentTypeInfo) { + parentTypeInfo.addCategories(newElement, categories); + toPopulateCategories = (JavaElementInfo)parentTypeInfo.getEnclosingType(); + } else { + break; + } + } boolean isDeprecated = isNodeDeprecated(node); newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); @@ -258,6 +285,17 @@ public boolean visit(EnumDeclaration node) { SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + char[][] categories = getCategories(node); + newInfo.addCategories(newElement, categories); + JavaElementInfo toPopulateCategories = this.infos.peek(); + while (toPopulateCategories != null) { + if (toPopulateCategories instanceof SourceTypeElementInfo parentTypeInfo) { + parentTypeInfo.addCategories(newElement, categories); + toPopulateCategories = (JavaElementInfo)parentTypeInfo.getEnclosingType(); + } else { + break; + } + } boolean isDeprecated = isNodeDeprecated(node); newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); @@ -273,27 +311,28 @@ public void endVisit(EnumDeclaration decl) { this.infos.pop(); } -// @Override -// public boolean visit(EnumConstantDeclaration node) { -// SourceField newElement = new SourceField(this.elements.peek(), node.getName().toString()); -// this.elements.push(newElement); -// addAsChild(this.infos.peek(), newElement); -// SourceFieldElementInfo info = new SourceFieldElementInfo(); -// info.setSourceRangeStart(node.getStartPosition()); -// info.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); -// info.setFlags(node.getFlags()); -// info.setNameSourceStart(node.getName().getStartPosition()); -// info.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); -// // TODO populate info -// this.infos.push(info); -// this.toPopulate.put(newElement, info); -// return true; -// } -// @Override -// public void endVisit(EnumConstantDeclaration decl) { -// this.elements.pop(); -// this.infos.pop(); -// } + @Override + public boolean visit(EnumConstantDeclaration node) { + SourceField newElement = new SourceField(this.elements.peek(), node.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceFieldElementInfo info = new SourceFieldElementInfo(); + info.setSourceRangeStart(node.getStartPosition()); + info.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + boolean isDeprecated = isNodeDeprecated(node); + info.setFlags(node.getModifiers() | ClassFileConstants.AccEnum | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); + info.setNameSourceStart(node.getName().getStartPosition()); + info.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + // TODO populate info + this.infos.push(info); + this.toPopulate.put(newElement, info); + return true; + } + @Override + public void endVisit(EnumConstantDeclaration decl) { + this.elements.pop(); + this.infos.pop(); + } @Override public boolean visit(RecordDeclaration node) { @@ -303,6 +342,17 @@ public boolean visit(RecordDeclaration node) { SourceTypeElementInfo newInfo = new SourceTypeElementInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + char[][] categories = getCategories(node); + newInfo.addCategories(newElement, categories); + JavaElementInfo toPopulateCategories = this.infos.peek(); + while (toPopulateCategories != null) { + if (toPopulateCategories instanceof SourceTypeElementInfo parentTypeInfo) { + parentTypeInfo.addCategories(newElement, categories); + toPopulateCategories = (JavaElementInfo)parentTypeInfo.getEnclosingType(); + } else { + break; + } + } boolean isDeprecated = isNodeDeprecated(node); newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); @@ -347,6 +397,9 @@ public boolean visit(MethodDeclaration method) { info.setReturnType("void".toCharArray()); //$NON-NLS-1$ } } + if (this.infos.peek() instanceof SourceTypeElementInfo parentInfo) { + parentInfo.addCategories(newElement, getCategories(method)); + } info.setSourceRangeStart(method.getStartPosition()); info.setSourceRangeEnd(method.getStartPosition() + method.getLength() - 1); boolean isDeprecated = isNodeDeprecated(method); @@ -381,6 +434,7 @@ public boolean visit(AnnotationTypeMemberDeclaration method) { info.setReturnType(method.getType().toString().toCharArray()); info.setSourceRangeStart(method.getStartPosition()); info.setSourceRangeEnd(method.getStartPosition() + method.getLength() - 1); + ((SourceTypeElementInfo)this.infos.peek()).addCategories(newElement, getCategories(method)); boolean isDeprecated = isNodeDeprecated(method); info.setFlags(method.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); info.setNameSourceStart(method.getName().getStartPosition()); @@ -534,15 +588,24 @@ public Entry memberValue(Expression dom) { value = token.substring(0, token.length() - 1); } if (value instanceof String valueString) { - value = switch (type) { - case IMemberValuePair.K_INT -> Integer.parseInt(valueString); - case IMemberValuePair.K_LONG -> Long.parseLong(valueString); - case IMemberValuePair.K_SHORT -> Short.parseShort(valueString); - case IMemberValuePair.K_BYTE -> Byte.parseByte(valueString); - case IMemberValuePair.K_FLOAT -> Float.parseFloat(valueString); - case IMemberValuePair.K_DOUBLE -> Double.parseDouble(valueString); - default -> throw new IllegalArgumentException("Type not (yet?) supported"); //$NON-NLS-1$ - }; + // I tried using `yield`, but this caused ECJ to throw an AIOOB, preventing compilation + switch (type) { + case IMemberValuePair.K_INT: { + try { + value = Integer.parseInt(valueString); + } catch (NumberFormatException e) { + type = IMemberValuePair.K_LONG; + value = Long.parseLong(valueString); + } + break; + } + case IMemberValuePair.K_LONG: value = Long.parseLong(valueString); break; + case IMemberValuePair.K_SHORT: value = Short.parseShort(valueString); break; + case IMemberValuePair.K_BYTE: value = Byte.parseByte(valueString); break; + case IMemberValuePair.K_FLOAT: value = Float.parseFloat(valueString); break; + case IMemberValuePair.K_DOUBLE: value = Double.parseDouble(valueString); break; + default: throw new IllegalArgumentException("Type not (yet?) supported"); //$NON-NLS-1$ + } } return new SimpleEntry<>(value, type); } @@ -620,16 +683,21 @@ private LocalVariable toLocalVariable(SingleVariableDeclaration parameter) { @Override public boolean visit(FieldDeclaration field) { - JavaElementInfo parent = this.infos.peek(); + JavaElementInfo parentInfo = this.infos.peek(); + JavaElement parentElement = this.elements.peek(); boolean isDeprecated = isNodeDeprecated(field); + char[][] categories = getCategories(field); for (VariableDeclarationFragment fragment : (Collection) field.fragments()) { - SourceField newElement = new SourceField(this.elements.peek(), fragment.getName().toString()); + SourceField newElement = new SourceField(parentElement, fragment.getName().toString()); this.elements.push(newElement); - addAsChild(parent, newElement); + addAsChild(parentInfo, newElement); SourceFieldElementInfo info = new SourceFieldElementInfo(); info.setTypeName(field.getType().toString().toCharArray()); info.setSourceRangeStart(field.getStartPosition()); info.setSourceRangeEnd(field.getStartPosition() + field.getLength() - 1); + if (parentInfo instanceof SourceTypeElementInfo parentTypeInfo) { + parentTypeInfo.addCategories(newElement, categories); + } info.setFlags(field.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); info.setNameSourceStart(fragment.getName().getStartPosition()); info.setNameSourceEnd(fragment.getName().getStartPosition() + fragment.getName().getLength() - 1); @@ -759,6 +827,18 @@ private ServiceInfo toServiceInfo(ProvidesDirective node) { return res; } private boolean isNodeDeprecated(BodyDeclaration node) { + if (node.getJavadoc() != null) { + boolean javadocDeprecated = node.getJavadoc().tags().stream() // + .anyMatch(tag -> { + return TagElement.TAG_DEPRECATED.equals(((AbstractTagElement)tag).getTagName()); + }); + if (javadocDeprecated) { + return true; + } + } + if (node.getAST().apiLevel() <= 2) { + return false; + } return ((List)node.modifiers()).stream() // .anyMatch(modifier -> { if (!isAnnotation(modifier)) { @@ -803,4 +883,26 @@ private boolean hasAlternativeDeprecated() { this.alternativeDeprecated = false; return this.alternativeDeprecated; } + private char[][] getCategories(BodyDeclaration decl) { + if (decl.getJavadoc() != null) { + return ((List)decl.getJavadoc().tags()).stream() // + .filter(tag -> "@category".equals(tag.getTagName()) && ((List)tag.fragments()).size() > 0) //$NON-NLS-1$ + .map(tag -> ((List)tag.fragments()).get(0)) // + .map(fragment -> { + String fragmentString = fragment.toString(); + /** + * I think this is a bug in JDT, but I am replicating the behaviour. + * + * @see CompilationUnitTests.testGetCategories13() + */ + int firstAsterix = fragmentString.indexOf('*'); + return fragmentString.substring(0, firstAsterix != -1 ? firstAsterix : fragmentString.length()); + }) // + .flatMap(fragment -> (Stream)Stream.of(fragment.split("\\s+"))) // //$NON-NLS-1$ + .filter(category -> category.length() > 0) // + .map(category -> (category).toCharArray()) // + .toArray(char[][]::new); + } + return new char[0][0]; + } } From aaa78432ad1c9b444ca716e354211f9e10e47190 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 12 Feb 2024 14:41:23 -0500 Subject: [PATCH 10/26] Keep track of types declared local to a method - Fixes ASTModelBridgeTests.testFindElement05 - Fixes ASTModelBridgeTests.testLocalType - Fixes ASTModelBridgeTests.testLocalType2 Signed-off-by: David Thompson --- .../eclipse/jdt/internal/core/DOMToModelPopulator.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 0173d318a87..a06851b1022 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -139,6 +139,12 @@ private static void addAsChild(JavaElementInfo parentInfo, IJavaElement childEle openable.addChild(childElement); return; } + if (parentInfo instanceof SourceMethodWithChildrenInfo method) { + IJavaElement[] newElements = Arrays.copyOf(method.children, method.children.length + 1); + newElements[newElements.length - 1] = childElement; + method.children = newElements; + return; + } } @Override @@ -385,7 +391,7 @@ public boolean visit(MethodDeclaration method) { .toArray(String[]::new)); this.elements.push(newElement); addAsChild(this.infos.peek(), newElement); - SourceMethodInfo info = new SourceMethodInfo(); + SourceMethodInfo info = new SourceMethodWithChildrenInfo(new IJavaElement[0]); info.setArgumentNames(((List)method.parameters()).stream().map(param -> param.getName().toString().toCharArray()).toArray(char[][]::new)); info.arguments = ((List)method.parameters()).stream() .map(this::toLocalVariable) From 553b7f7746a3b64eb57e28f56996c9a3e6bd24cc Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Tue, 13 Feb 2024 15:54:16 +0100 Subject: [PATCH 11/26] [DOM to Model] fix exception and varargs translation --- .../eclipse/jdt/internal/core/DOMToModelPopulator.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index a06851b1022..02e0d56a913 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -22,6 +22,7 @@ import java.util.stream.Stream; import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IJavaElement; @@ -29,6 +30,7 @@ import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.compiler.InvalidInputException; +import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AbstractTagElement; @@ -406,10 +408,15 @@ public boolean visit(MethodDeclaration method) { if (this.infos.peek() instanceof SourceTypeElementInfo parentInfo) { parentInfo.addCategories(newElement, getCategories(method)); } + if (method.getAST().apiLevel() >= AST.JLS8) { + info.setExceptionTypeNames(((List)method.thrownExceptionTypes()).stream().map(Type::toString).map(String::toCharArray).toArray(char[][]::new)); + } info.setSourceRangeStart(method.getStartPosition()); info.setSourceRangeEnd(method.getStartPosition() + method.getLength() - 1); boolean isDeprecated = isNodeDeprecated(method); - info.setFlags(method.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); + info.setFlags(method.getModifiers() + | (isDeprecated ? ClassFileConstants.AccDeprecated : 0) + | (((List)method.parameters()).stream().anyMatch(decl -> decl.isVarargs()) ? Flags.AccVarargs : 0)); info.setNameSourceStart(method.getName().getStartPosition()); info.setNameSourceEnd(method.getName().getStartPosition() + method.getName().getLength() - 1); this.infos.push(info); From 0484f6a8923f95605379c297e31d40412d07d308 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Tue, 13 Feb 2024 18:54:53 +0100 Subject: [PATCH 12/26] [DOM To Model] Improve some types --- .../jdt/internal/core/DOMToModelPopulator.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 02e0d56a913..6d529c1eacd 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -227,9 +227,21 @@ public boolean visit(TypeDeclaration node) { break; } } + if (node.getAST().apiLevel() > 2) { + newInfo.setSuperInterfaceNames(((List)node.superInterfaceTypes()).stream().map(Type::toString).map(String::toCharArray).toArray(char[][]::new)); + } + if (node.getSuperclassType() != null) { + newInfo.setSuperclassName(node.getSuperclassType().toString().toCharArray()); + } + if (node.getAST().apiLevel() >= AST.JLS17) { + newInfo.setPermittedSubtypeNames(((List)node.permittedTypes()).stream().map(Type::toString).map(String::toCharArray).toArray(char[][]::new)); + } newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); - newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); + newInfo.setFlags(node.getModifiers() + | (isDeprecated ? ClassFileConstants.AccDeprecated : 0) + | (node.isInterface() ? Flags.AccInterface : 0)); + newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); @@ -321,10 +333,12 @@ public void endVisit(EnumDeclaration decl) { @Override public boolean visit(EnumConstantDeclaration node) { + IJavaElement parent = this.elements.peek(); SourceField newElement = new SourceField(this.elements.peek(), node.getName().toString()); this.elements.push(newElement); addAsChild(this.infos.peek(), newElement); SourceFieldElementInfo info = new SourceFieldElementInfo(); + info.setTypeName(parent.getElementName().toCharArray()); info.setSourceRangeStart(node.getStartPosition()); info.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); boolean isDeprecated = isNodeDeprecated(node); @@ -362,7 +376,7 @@ public boolean visit(RecordDeclaration node) { } } boolean isDeprecated = isNodeDeprecated(node); - newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); + newInfo.setFlags(node.getModifiers() | Flags.AccRecord | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); From ea9744fe599d57d419245391209e3ea9f5305df1 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 12 Feb 2024 16:33:25 -0500 Subject: [PATCH 13/26] Record anonymous classes in model - Fixes `ASTModelBridgeTests.testAnonymousType` - Fixes `ASTModelBridgeTests.testAnonymousType2` Signed-off-by: David Thompson --- .../internal/core/DOMToModelPopulator.java | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 6d529c1eacd..afed59eb4e0 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -36,10 +36,12 @@ import org.eclipse.jdt.core.dom.AbstractTagElement; import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.ExportsDirective; @@ -63,11 +65,12 @@ import org.eclipse.jdt.core.dom.RecordDeclaration; import org.eclipse.jdt.core.dom.RequiresDirective; import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StringLiteral; -import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.UsesDirective; @@ -572,6 +575,65 @@ public void endVisit(SingleMemberAnnotation decl) { this.infos.pop(); } + @Override + public boolean visit(AnonymousClassDeclaration decl) { + if (decl.getParent() instanceof ClassInstanceCreation constructorInvocation) { + if (constructorInvocation.getAST().apiLevel() > 2) { + ((List)constructorInvocation.typeArguments()) + .stream() + .map(SimpleType::getName) + .map(Name::getFullyQualifiedName) + .forEach(this.currentTypeParameters::add); + } + SourceType newElement = new SourceType(this.elements.peek(), ""); //$NON-NLS-1$ + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceTypeElementInfo newInfo = new SourceTypeElementInfo() { + @Override + public boolean isAnonymousMember() { + return true; + } + }; + JavaElementInfo toPopulateCategories = this.infos.peek(); + while (toPopulateCategories != null) { + if (toPopulateCategories instanceof SourceTypeElementInfo parentTypeInfo) { + toPopulateCategories = (JavaElementInfo)parentTypeInfo.getEnclosingType(); + } else { + break; + } + } + + newInfo.setSourceRangeEnd(decl.getStartPosition() + decl.getLength() - 1); + newInfo.setHandle(newElement); + if (constructorInvocation.getAST().apiLevel() > 2) { + newInfo.setNameSourceStart(constructorInvocation.getType().getStartPosition()); + newInfo.setSourceRangeStart(constructorInvocation.getType().getStartPosition()); + newInfo.setNameSourceEnd(constructorInvocation.getType().getStartPosition() + constructorInvocation.getType().getLength() - 1); + } else { + newInfo.setNameSourceStart(constructorInvocation.getName().getStartPosition()); + newInfo.setSourceRangeStart(constructorInvocation.getName().getStartPosition()); + newInfo.setNameSourceEnd(constructorInvocation.getName().getStartPosition() + constructorInvocation.getName().getLength() - 1); + } + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + } + return true; + } + @Override + public void endVisit(AnonymousClassDeclaration decl) { + if (decl.getParent() instanceof ClassInstanceCreation constructorInvocation) { + this.elements.pop(); + this.infos.pop(); + if (constructorInvocation.getAST().apiLevel() > 2) { + ((List)constructorInvocation.typeArguments()) + .stream() + .map(SimpleType::getName) + .map(Name::getFullyQualifiedName) + .forEach(this.currentTypeParameters::remove); + } + } + } + public Entry memberValue(Expression dom) { if (dom == null || dom instanceof NullLiteral nullLiteral || From e94788f70e0c4a79229713cf40476a49f8049588 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Tue, 13 Feb 2024 22:31:41 +0100 Subject: [PATCH 14/26] [DOM To Model] Fix some flags --- .../org/eclipse/jdt/internal/core/DOMToModelPopulator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index afed59eb4e0..9b7f3330e60 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -285,7 +285,7 @@ public boolean visit(AnnotationTypeDeclaration node) { } } boolean isDeprecated = isNodeDeprecated(node); - newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); + newInfo.setFlags(node.getModifiers() | ClassFileConstants.AccInterface|Flags.AccAnnotation | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); @@ -320,7 +320,7 @@ public boolean visit(EnumDeclaration node) { } } boolean isDeprecated = isNodeDeprecated(node); - newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); + newInfo.setFlags(node.getModifiers() | Flags.AccEnum | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); From 737db3c53cea0be985fc82114ad9e1332e585d5b Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 14 Feb 2024 11:04:40 -0500 Subject: [PATCH 15/26] Fix regression caused by superclass fix Signed-off-by: David Thompson --- .../org/eclipse/jdt/internal/core/DOMToModelPopulator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 9b7f3330e60..9fe5fe89e39 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -233,7 +233,7 @@ public boolean visit(TypeDeclaration node) { if (node.getAST().apiLevel() > 2) { newInfo.setSuperInterfaceNames(((List)node.superInterfaceTypes()).stream().map(Type::toString).map(String::toCharArray).toArray(char[][]::new)); } - if (node.getSuperclassType() != null) { + if (node.getAST().apiLevel() > 2 && node.getSuperclassType() != null) { newInfo.setSuperclassName(node.getSuperclassType().toString().toCharArray()); } if (node.getAST().apiLevel() >= AST.JLS17) { @@ -244,7 +244,7 @@ public boolean visit(TypeDeclaration node) { newInfo.setFlags(node.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0) | (node.isInterface() ? Flags.AccInterface : 0)); - + newInfo.setHandle(newElement); newInfo.setNameSourceStart(node.getName().getStartPosition()); newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); From c616e8f2c978b78c5e797c9c972f6b1f90ca0066 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Wed, 14 Feb 2024 18:07:37 +0100 Subject: [PATCH 16/26] [DOM To Model] Pass compiler options to ASTParser --- .../model/org/eclipse/jdt/internal/core/CompilationUnit.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index 4f5cfb011de..94f8a1e085a 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -161,6 +161,7 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito astParser.setStatementsRecovery((reconcileFlags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); astParser.setResolveBindings(resolveBindings); astParser.setBindingsRecovery((reconcileFlags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0); + astParser.setCompilerOptions(options); if (astParser.createAST(pm) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { if (perWorkingCopyInfo != null) { try { From 30b1564847b247b8764ece74f4600c1f206db1da Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Thu, 15 Feb 2024 17:30:34 +0100 Subject: [PATCH 17/26] [DOM To Model] better support for record and constructors --- .../internal/core/DOMToModelPopulator.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 9fe5fe89e39..6f188f629e2 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -393,6 +393,31 @@ public void endVisit(RecordDeclaration decl) { this.infos.pop(); } + @Override + public boolean visit(SingleVariableDeclaration node) { + if (node.getParent() instanceof RecordDeclaration) { + SourceField newElement = new SourceField(this.elements.peek(), node.getName().toString()); + this.elements.push(newElement); + addAsChild(this.infos.peek(), newElement); + SourceFieldElementInfo newInfo = new SourceFieldElementInfo(); + newInfo.setSourceRangeStart(node.getStartPosition()); + newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.setNameSourceStart(node.getName().getStartPosition()); + newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + newInfo.setTypeName(node.getType().toString().toCharArray()); + newInfo.isRecordComponent = true; + this.infos.push(newInfo); + this.toPopulate.put(newElement, newInfo); + } + return true; + } + @Override + public void endVisit(SingleVariableDeclaration decl) { + if (decl.getParent() instanceof RecordDeclaration recordDecl) { + this.elements.pop(); + this.infos.pop(); + } + } @Override public boolean visit(MethodDeclaration method) { @@ -403,18 +428,25 @@ public boolean visit(MethodDeclaration method) { .map(Name::getFullyQualifiedName) .forEach(this.currentTypeParameters::add); } + List parameters = method.parameters(); + if (method.getAST().apiLevel() >= AST.JLS16 + && method.isCompactConstructor() + && (parameters == null || parameters.isEmpty()) + && method.getParent() instanceof RecordDeclaration parentRecord) { + parameters = parentRecord.recordComponents(); + } SourceMethod newElement = new SourceMethod(this.elements.peek(), method.getName().getIdentifier(), - ((List)method.parameters()).stream() + parameters.stream() .map(this::createSignature) .toArray(String[]::new)); this.elements.push(newElement); addAsChild(this.infos.peek(), newElement); - SourceMethodInfo info = new SourceMethodWithChildrenInfo(new IJavaElement[0]); - info.setArgumentNames(((List)method.parameters()).stream().map(param -> param.getName().toString().toCharArray()).toArray(char[][]::new)); - info.arguments = ((List)method.parameters()).stream() - .map(this::toLocalVariable) - .toArray(LocalVariable[]::new); + SourceMethodElementInfo info = method.isConstructor() ? + new SourceConstructorInfo() : + new SourceMethodWithChildrenInfo(new IJavaElement[0]); + info.setArgumentNames(parameters.stream().map(param -> param.getName().toString().toCharArray()).toArray(char[][]::new)); + info.arguments = parameters.stream().map(this::toLocalVariable).toArray(LocalVariable[]::new); if (method.getAST().apiLevel() > 2) { if (method.getReturnType2() != null) { info.setReturnType(method.getReturnType2().toString().toCharArray()); @@ -436,6 +468,7 @@ public boolean visit(MethodDeclaration method) { | (((List)method.parameters()).stream().anyMatch(decl -> decl.isVarargs()) ? Flags.AccVarargs : 0)); info.setNameSourceStart(method.getName().getStartPosition()); info.setNameSourceEnd(method.getName().getStartPosition() + method.getName().getLength() - 1); + info.isCanonicalConstructor = method.isConstructor(); this.infos.push(info); this.toPopulate.put(newElement, info); return true; From 24ed9fb52d2d35c10849e82c66c61ef18dec8787 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Fri, 16 Feb 2024 12:07:11 +0100 Subject: [PATCH 18/26] [DOM To Model] Set field constants + Fix on-demand import name range + Fix TypeParameter name range + Fix some annotation name range + Fix some anonymous source range --- .../internal/core/DOMToModelPopulator.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 6f188f629e2..c66db111524 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -42,6 +42,7 @@ import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.CharacterLiteral; import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.Comment; import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.ExportsDirective; @@ -194,7 +195,28 @@ public boolean visit(ImportDeclaration node) { newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); newInfo.setNameSourceStart(node.getName().getStartPosition()); - newInfo.setNameSourceEnd(node.getName().getStartPosition() + node.getName().getLength() - 1); + int nameSourceEnd = node.getName().getStartPosition() + node.getName().getLength() - 1; + if (node.isOnDemand()) { + nameSourceEnd = node.getStartPosition() + node.getLength() - 1; + char[] contents = this.root.getContents(); + List comments = domUnit(node).getCommentList(); + boolean changed = false; + do { + while (contents[nameSourceEnd] == ';' || Character.isWhitespace(contents[nameSourceEnd])) { + nameSourceEnd--; + changed = true; + } + final int currentEnd = nameSourceEnd; + int newEnd = comments.stream() + .filter(comment -> comment.getStartPosition() <= currentEnd && comment.getStartPosition() + comment.getLength() >= currentEnd) + .findAny() + .map(comment -> comment.getStartPosition() - 1) + .orElse(currentEnd); + changed = (currentEnd != newEnd); + nameSourceEnd = newEnd; + } while (changed); + } + newInfo.setNameSourceEnd(nameSourceEnd); this.infos.push(newInfo); this.toPopulate.put(newElement, newInfo); return true; @@ -527,7 +549,7 @@ public boolean visit(org.eclipse.jdt.core.dom.TypeParameter node) { addAsChild(this.infos.peek(), newElement); TypeParameterElementInfo info = new TypeParameterElementInfo(); info.setSourceRangeStart(node.getStartPosition()); - info.setSourceRangeEnd(node.getStartPosition() + node.getLength()); + info.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); info.nameStart = node.getName().getStartPosition(); info.nameEnd = node.getName().getStartPosition() + node.getName().getLength() - 1; info.bounds = ((List)node.typeBounds()).stream().map(Type::toString).map(String::toCharArray).toArray(char[][]::new); @@ -577,6 +599,8 @@ public boolean visit(MarkerAnnotation node) { AnnotationInfo newInfo = new AnnotationInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.nameStart = node.getTypeName().getStartPosition(); + newInfo.nameEnd = node.getTypeName().getStartPosition() + node.getTypeName().getLength() - 1; newInfo.members = new IMemberValuePair[0]; this.infos.push(newInfo); this.toPopulate.put(newElement, newInfo); @@ -596,6 +620,8 @@ public boolean visit(SingleMemberAnnotation node) { AnnotationInfo newInfo = new AnnotationInfo(); newInfo.setSourceRangeStart(node.getStartPosition()); newInfo.setSourceRangeEnd(node.getStartPosition() + node.getLength() - 1); + newInfo.nameStart = node.getTypeName().getStartPosition(); + newInfo.nameEnd = node.getTypeName().getStartPosition() + node.getTypeName().getLength() - 1; Entry value = memberValue(node.getValue()); newInfo.members = new IMemberValuePair[] { new org.eclipse.jdt.internal.core.MemberValuePair("value", value.getKey(), value.getValue()) }; //$NON-NLS-1$ this.infos.push(newInfo); @@ -640,7 +666,7 @@ public boolean isAnonymousMember() { newInfo.setHandle(newElement); if (constructorInvocation.getAST().apiLevel() > 2) { newInfo.setNameSourceStart(constructorInvocation.getType().getStartPosition()); - newInfo.setSourceRangeStart(constructorInvocation.getType().getStartPosition()); + newInfo.setSourceRangeStart(constructorInvocation.getStartPosition()); newInfo.setNameSourceEnd(constructorInvocation.getType().getStartPosition() + constructorInvocation.getType().getLength() - 1); } else { newInfo.setNameSourceStart(constructorInvocation.getName().getStartPosition()); @@ -823,6 +849,12 @@ public boolean visit(FieldDeclaration field) { info.setFlags(field.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0)); info.setNameSourceStart(fragment.getName().getStartPosition()); info.setNameSourceEnd(fragment.getName().getStartPosition() + fragment.getName().getLength() - 1); + Expression initializer = fragment.getInitializer(); + if (((field.getParent() instanceof TypeDeclaration type && type.isInterface()) + || Flags.isFinal(field.getModifiers())) + && initializer != null && initializer.getStartPosition() >= 0) { + info.initializationSource = Arrays.copyOfRange(this.root.getContents(), initializer.getStartPosition(), initializer.getStartPosition() + initializer.getLength()); + } // TODO populate info this.infos.push(info); this.toPopulate.put(newElement, info); @@ -1027,4 +1059,11 @@ private char[][] getCategories(BodyDeclaration decl) { } return new char[0][0]; } + + private static org.eclipse.jdt.core.dom.CompilationUnit domUnit(ASTNode node) { + while (node != null && !(node instanceof org.eclipse.jdt.core.dom.CompilationUnit)) { + node = node.getParent(); + } + return (org.eclipse.jdt.core.dom.CompilationUnit)node; + } } From 741e3171e51ef406135bee89e9e46b85723fe969 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Fri, 16 Feb 2024 12:41:43 +0100 Subject: [PATCH 19/26] [DOM To Model] Fix processing Class with sources --- .../model/org/eclipse/jdt/internal/core/CompilationUnit.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index 94f8a1e085a..bd275b29f07 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -153,10 +153,11 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito openBuffer(pm, unitInfo); // open buffer independently from the info, since we are building the info } + CompilationUnit source = cloneCachingContents(); if (DOM_BASED_OPERATIONS) { ASTParser astParser = ASTParser.newParser(info instanceof ASTHolderCUInfo astHolder && astHolder.astLevel > 0 ? astHolder.astLevel : AST.getJLSLatest()); astParser.setWorkingCopyOwner(getOwner()); - astParser.setSource(this); + astParser.setSource(source); astParser.setProject(getJavaProject()); astParser.setStatementsRecovery((reconcileFlags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); astParser.setResolveBindings(resolveBindings); @@ -205,7 +206,6 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito // compute other problems if needed CompilationUnitDeclaration compilationUnitDeclaration = null; - CompilationUnit source = cloneCachingContents(); try { if (computeProblems) { if (problems == null) { From f521f4a41d99c3349117792a68ffe868409e53dd Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Fri, 16 Feb 2024 14:45:40 +0100 Subject: [PATCH 20/26] Fix not reporting problems directly when a placeholder is provided Instead of directly calling the problemReporter, try first to add the problems to ASTHolverCUInfo.problems when provided. --- .../jdt/internal/core/CompilationUnit.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index bd275b29f07..5cc48e4672e 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.jdt.core.*; @@ -154,6 +156,7 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito } CompilationUnit source = cloneCachingContents(); + Map problems = info instanceof ASTHolderCUInfo astHolder ? astHolder.problems : null; if (DOM_BASED_OPERATIONS) { ASTParser astParser = ASTParser.newParser(info instanceof ASTHolderCUInfo astHolder && astHolder.astLevel > 0 ? astHolder.astLevel : AST.getJLSLatest()); astParser.setWorkingCopyOwner(getOwner()); @@ -164,14 +167,20 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito astParser.setBindingsRecovery((reconcileFlags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0); astParser.setCompilerOptions(options); if (astParser.createAST(pm) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { - if (perWorkingCopyInfo != null) { - try { - perWorkingCopyInfo.beginReporting(); - for (IProblem problem : newAST.getProblems()) { - perWorkingCopyInfo.acceptProblem(problem); + if (computeProblems) { + if (perWorkingCopyInfo != null && problems == null) { + try { + perWorkingCopyInfo.beginReporting(); + for (IProblem problem : newAST.getProblems()) { + perWorkingCopyInfo.acceptProblem(problem); + } + } finally { + perWorkingCopyInfo.endReporting(); } - } finally { - perWorkingCopyInfo.endReporting(); + } else if (newAST.getProblems().length > 0) { + problems.put(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, Stream.of(newAST.getProblems()).filter(CategorizedProblem.class::isInstance) + .map(CategorizedProblem.class::cast) + .toArray(CategorizedProblem[]::new)); } } if (info instanceof ASTHolderCUInfo astHolder) { @@ -188,7 +197,6 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito } } else { CompilationUnitStructureRequestor requestor = new CompilationUnitStructureRequestor(this, unitInfo, newElements); - Map problems = info instanceof ASTHolderCUInfo astHolder ? astHolder.problems : null; IProblemFactory problemFactory = new DefaultProblemFactory(); SourceElementParser parser = new SourceElementParser( requestor, From 1656b255a0a0e6b214318199c8cbf046c7c3dba1 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Fri, 16 Feb 2024 23:13:34 +0100 Subject: [PATCH 21/26] [DOM To Model] Fix LocalVariable type signature --- .../org/eclipse/jdt/internal/core/DOMToModelPopulator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index c66db111524..74249fa4dff 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -820,10 +820,10 @@ private LocalVariable toLocalVariable(SingleVariableDeclaration parameter) { return new LocalVariable(this.elements.peek(), parameter.getName().getIdentifier(), parameter.getStartPosition(), - parameter.getStartPosition() + parameter.getLength(), + parameter.getStartPosition() + parameter.getLength() - 1, parameter.getName().getStartPosition(), - parameter.getName().getStartPosition() + parameter.getName().getLength(), - parameter.getType().toString(), + parameter.getName().getStartPosition() + parameter.getName().getLength() - 1, + Util.getSignature(parameter.getType()), null, // TODO parameter.getFlags(), parameter.getParent() instanceof MethodDeclaration); From 2aec28b84e0cd56912fa9266fead455249dac27b Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Sun, 18 Feb 2024 13:56:43 +0100 Subject: [PATCH 22/26] [DOM To Model] Fix when astLevel == 0 --- .../org/eclipse/jdt/internal/core/DOMToModelPopulator.java | 4 ++-- .../jdt/internal/core/ReconcileWorkingCopyOperation.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java index 74249fa4dff..64d9bef42e1 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -487,7 +487,7 @@ public boolean visit(MethodDeclaration method) { boolean isDeprecated = isNodeDeprecated(method); info.setFlags(method.getModifiers() | (isDeprecated ? ClassFileConstants.AccDeprecated : 0) - | (((List)method.parameters()).stream().anyMatch(decl -> decl.isVarargs()) ? Flags.AccVarargs : 0)); + | ((method.getAST().apiLevel() > AST.JLS2 && ((List)method.parameters()).stream().anyMatch(SingleVariableDeclaration::isVarargs)) ? Flags.AccVarargs : 0)); info.setNameSourceStart(method.getName().getStartPosition()); info.setNameSourceEnd(method.getName().getStartPosition() + method.getName().getLength() - 1); info.isCanonicalConstructor = method.isConstructor(); @@ -873,7 +873,7 @@ public void endVisit(FieldDeclaration decl) { private String createSignature(SingleVariableDeclaration decl) { String initialSignature = Util.getSignature(decl.getType()); int extraDimensions = decl.getExtraDimensions(); - if (decl.isVarargs()) { + if (decl.getAST().apiLevel() > AST.JLS2 && decl.isVarargs()) { extraDimensions++; } return Signature.createArraySignature(initialSignature, extraDimensions); diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java index 6b949377eb3..7f51138139f 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java @@ -190,7 +190,7 @@ public org.eclipse.jdt.core.dom.CompilationUnit makeConsistent(CompilationUnit w } Map options = workingCopy.getJavaProject().getOptions(true); if (CompilationUnit.DOM_BASED_OPERATIONS) { - ASTParser parser = ASTParser.newParser(this.astLevel >= 0 ? this.astLevel : AST.getJLSLatest()); + ASTParser parser = ASTParser.newParser(this.astLevel > 0 ? this.astLevel : AST.getJLSLatest()); parser.setResolveBindings(this.resolveBindings || (this.reconcileFlags & ICompilationUnit.FORCE_PROBLEM_DETECTION) != 0); parser.setCompilerOptions(options); parser.setSource(source); From f6eebd9e319c5a888aceadfc2795f177036d510c Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Sun, 18 Feb 2024 14:21:53 +0100 Subject: [PATCH 23/26] Honor ICompilationUnit.IGNORE_METHOD_BODIES --- .../model/org/eclipse/jdt/internal/core/CompilationUnit.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index 5cc48e4672e..1eaedb59704 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -165,6 +165,7 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito astParser.setStatementsRecovery((reconcileFlags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); astParser.setResolveBindings(resolveBindings); astParser.setBindingsRecovery((reconcileFlags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0); + astParser.setIgnoreMethodBodies((reconcileFlags & ICompilationUnit.IGNORE_METHOD_BODIES) != 0); astParser.setCompilerOptions(options); if (astParser.createAST(pm) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { if (computeProblems) { From a125ef408d891b9f4c94ba8b88ddc39380bfca43 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Mon, 19 Feb 2024 14:39:50 +0100 Subject: [PATCH 24/26] codeSelect support more binding types --- .../org/eclipse/jdt/internal/core/CompilationUnit.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index 1eaedb59704..ec39fe3d3f8 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -32,6 +32,7 @@ import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.MethodReference; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.Type; @@ -473,6 +474,12 @@ static IBinding resolveBinding(ASTNode node) { } return resolveBinding(aName.getParent()); } + if (node instanceof org.eclipse.jdt.core.dom.LambdaExpression lambda) { + return lambda.resolveMethodBinding(); + } + if (node instanceof MethodReference methodRef) { + return methodRef.resolveMethodBinding(); + } if (node instanceof org.eclipse.jdt.core.dom.TypeParameter typeParameter) { return typeParameter.resolveBinding(); } From 28fc268eae80762fe6a05014af1eb4f03acce1fc Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Mon, 19 Feb 2024 14:50:57 +0100 Subject: [PATCH 25/26] Fix node used by codeSelect --- .../model/org/eclipse/jdt/internal/core/CompilationUnit.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index ec39fe3d3f8..0e7f2425457 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -441,7 +441,8 @@ public IJavaElement[] codeSelect(int offset, int length, WorkingCopyOwner workin if (currentAST == null) { return new IJavaElement[0]; } - ASTNode node = NodeFinder.perform(currentAST, offset, length); + NodeFinder finder = new NodeFinder(currentAST, offset, length); + ASTNode node = finder.getCoveredNode() != null ? finder.getCoveredNode() : finder.getCoveringNode(); IBinding binding = resolveBinding(node); if (binding != null) { return new IJavaElement[] { binding.getJavaElement() }; From c42b1ae32087cf6c0e38db24a9b1b3d2c180f214 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Wed, 21 Feb 2024 09:08:46 +0100 Subject: [PATCH 26/26] [DOM To Model] buildStructure better honor computeProblems Enable resolution when problems are requested as well. --- .../model/org/eclipse/jdt/internal/core/CompilationUnit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index 0e7f2425457..37ddc18f555 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -164,7 +164,7 @@ protected boolean buildStructure(OpenableElementInfo info, final IProgressMonito astParser.setSource(source); astParser.setProject(getJavaProject()); astParser.setStatementsRecovery((reconcileFlags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); - astParser.setResolveBindings(resolveBindings); + astParser.setResolveBindings(computeProblems || resolveBindings); astParser.setBindingsRecovery((reconcileFlags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0); astParser.setIgnoreMethodBodies((reconcileFlags & ICompilationUnit.IGNORE_METHOD_BODIES) != 0); astParser.setCompilerOptions(options);