From 1dc1fdfa26c949d1ff1d98d591f83882434f2a50 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Wed, 10 Jul 2024 17:33:25 +0200 Subject: [PATCH] A flag to resolve codeSelect using ASTParser Introduce a CompilationUnit.DOM_BASED_OPERATIONS flag which instead of using the ECJ-derived parser uses ASTParser to create a DOM and then uses the DOM to implement codeSelect This has the benefit of: * Simplifying the code as an crawling DOM is usually simpler than dealing with lower-level parser * allowing other parsers as backend if ASTParser is configured for it --- .../internal/codeassist/DOMCodeSelector.java | 650 ++++++++++++++++++ .../jdt/internal/core/ASTHolderCUInfo.java | 2 +- .../jdt/internal/core/CompilationUnit.java | 103 ++- .../internal/core/DOMToModelPopulator.java | 83 +++ .../eclipse/jdt/internal/core/util/Util.java | 2 +- 5 files changed, 832 insertions(+), 8 deletions(-) create mode 100644 org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCodeSelector.java create mode 100644 org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCodeSelector.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCodeSelector.java new file mode 100644 index 00000000000..daa90b8a4fc --- /dev/null +++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCodeSelector.java @@ -0,0 +1,650 @@ +/******************************************************************************* + * Copyright (c) 2024 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.codeassist; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaModelStatusConstants; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.ILocalVariable; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IParent; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ISourceReference; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.Comment; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.ExpressionMethodReference; +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.IPackageBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.LambdaExpression; +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.ParameterizedType; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.QualifiedType; +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.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeMethodReference; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeNameMatchRequestor; +import org.eclipse.jdt.internal.core.AnnotatableInfo; +import org.eclipse.jdt.internal.core.CompilationUnit; +import org.eclipse.jdt.internal.core.DOMToModelPopulator; +import org.eclipse.jdt.internal.core.JavaElement; +import org.eclipse.jdt.internal.core.LocalVariable; +import org.eclipse.jdt.internal.core.SourceField; +import org.eclipse.jdt.internal.core.SourceMethod; +import org.eclipse.jdt.internal.core.search.BasicSearchEngine; +import org.eclipse.jdt.internal.core.search.TypeNameMatchRequestorWrapper; +import org.eclipse.jdt.internal.core.util.Util; + +/** + * A util to select relevant IJavaElement from a DOM (as opposed to {@link SelectionEngine} + * which processes it using lower-level ECJ parser) + */ +public class DOMCodeSelector { + + private final CompilationUnit unit; + private final WorkingCopyOwner owner; + + public DOMCodeSelector(CompilationUnit unit, WorkingCopyOwner owner) { + this.unit = unit; + this.owner = owner; + } + + public IJavaElement[] codeSelect(int offset, int length) throws JavaModelException { + if (offset < 0) { + throw new JavaModelException(new IndexOutOfBoundsException(offset), IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS); + } + if (offset + length > this.unit.getSource().length()) { + throw new JavaModelException(new IndexOutOfBoundsException(offset + length), IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS); + } + org.eclipse.jdt.core.dom.CompilationUnit currentAST = this.unit.getOrBuildAST(this.owner); + if (currentAST == null) { + return new IJavaElement[0]; + } + String rawText = this.unit.getSource().substring(offset, offset + length); + int initialOffset = offset, initialLength = length; + boolean insideComment = ((List)currentAST.getCommentList()).stream() + .anyMatch(comment -> comment.getStartPosition() <= initialOffset && comment.getStartPosition() + comment.getLength() >= initialOffset + initialLength); + if (!insideComment) { // trim whitespaces and surrounding comments + boolean changed = false; + do { + changed = false; + if (length > 0 && Character.isWhitespace(this.unit.getSource().charAt(offset))) { + offset++; + length--; + changed = true; + } + if (length > 0 && Character.isWhitespace(this.unit.getSource().charAt(offset + length - 1))) { + length--; + changed = true; + } + List comments = currentAST.getCommentList(); + // leading comment + int offset1 = offset, length1 = length; + OptionalInt leadingCommentEnd = comments.stream().filter(comment -> { + int commentEndOffset = comment.getStartPosition() + comment.getLength() -1; + return comment.getStartPosition() <= offset1 && commentEndOffset > offset1 && commentEndOffset < offset1 + length1 - 1; + }).mapToInt(comment -> comment.getStartPosition() + comment.getLength() - 1) + .findAny(); + if (length > 0 && leadingCommentEnd.isPresent()) { + changed = true; + int newStart = leadingCommentEnd.getAsInt(); + int removedLeading = newStart + 1 - offset; + offset = newStart + 1; + length -= removedLeading; + } + // Trailing comment + int offset2 = offset, length2 = length; + OptionalInt trailingCommentStart = comments.stream().filter(comment -> { + return comment.getStartPosition() >= offset2 + && comment.getStartPosition() < offset2 + length2 + && comment.getStartPosition() + comment.getLength() > offset2 + length2; + }).mapToInt(Comment::getStartPosition) + .findAny(); + if (length > 0 && trailingCommentStart.isPresent()) { + changed = true; + int newEnd = trailingCommentStart.getAsInt(); + int removedTrailing = offset + length - 1 - newEnd; + length -= removedTrailing; + } + } while (changed); + } + String trimmedText = rawText.trim(); + NodeFinder finder = new NodeFinder(currentAST, offset, length); + final ASTNode node = finder.getCoveredNode() != null && finder.getCoveredNode().getStartPosition() > offset && finder.getCoveringNode().getStartPosition() + finder.getCoveringNode().getLength() > offset + length ? + finder.getCoveredNode() : + finder.getCoveringNode(); + if (node instanceof TagElement tagElement && TagElement.TAG_INHERITDOC.equals(tagElement.getTagName())) { + ASTNode javadocNode = node; + while (javadocNode != null && !(javadocNode instanceof Javadoc)) { + javadocNode = javadocNode.getParent(); + } + if (javadocNode instanceof Javadoc javadoc) { + ASTNode parent = javadoc.getParent(); + IBinding binding = resolveBinding(parent); + if (binding instanceof IMethodBinding methodBinding) { + var typeBinding = methodBinding.getDeclaringClass(); + if (typeBinding != null) { + List types = new ArrayList<>(Arrays.asList(typeBinding.getInterfaces())); + if (typeBinding.getSuperclass() != null) { + types.add(typeBinding.getSuperclass()); + } + while (!types.isEmpty()) { + ITypeBinding type = types.remove(0); + for (IMethodBinding m : Arrays.stream(type.getDeclaredMethods()).filter(methodBinding::overrides).toList()) { + if (m.getJavaElement() instanceof IMethod methodElement && methodElement.getJavadocRange() != null) { + return new IJavaElement[] { methodElement }; + } else { + types.addAll(Arrays.asList(type.getInterfaces())); + if (type.getSuperclass() != null) { + types.add(type.getSuperclass()); + } + } + } + } + } + IJavaElement element = methodBinding.getJavaElement(); + if (element != null) { + return new IJavaElement[] { element }; + } + } + } + } + org.eclipse.jdt.core.dom.ImportDeclaration importDecl = findImportDeclaration(node); + if (node instanceof ExpressionMethodReference emr && + emr.getExpression().getStartPosition() + emr.getExpression().getLength() <= offset && offset + length <= emr.getName().getStartPosition()) { + if (!(rawText.isEmpty() || rawText.equals(":") || rawText.equals("::"))) { //$NON-NLS-1$ //$NON-NLS-2$ + return new IJavaElement[0]; + } + if (emr.getParent() instanceof MethodInvocation methodInvocation) { + int index = methodInvocation.arguments().indexOf(emr); + return new IJavaElement[] {methodInvocation.resolveMethodBinding().getParameterTypes()[index].getDeclaredMethods()[0].getJavaElement()}; + } + if (emr.getParent() instanceof VariableDeclaration variableDeclaration) { + ITypeBinding requestedType = variableDeclaration.resolveBinding().getType(); + if (requestedType.getDeclaredMethods().length == 1 + && requestedType.getDeclaredMethods()[0].getJavaElement() instanceof IMethod overridenMethod) { + return new IJavaElement[] { overridenMethod }; + } + } + } + if (node instanceof LambdaExpression lambda) { + if (!(rawText.isEmpty() || rawText.equals("-") || rawText.equals(">") || rawText.equals("->"))) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return new IJavaElement[0]; // as requested by some tests + } + if (lambda.resolveMethodBinding() != null + && lambda.resolveMethodBinding().getMethodDeclaration() != null + && lambda.resolveMethodBinding().getMethodDeclaration().getJavaElement() != null) { + return new IJavaElement[] { lambda.resolveMethodBinding().getMethodDeclaration().getJavaElement() }; + } + } + if (importDecl != null && importDecl.isStatic()) { + IBinding importBinding = importDecl.resolveBinding(); + if (importBinding instanceof IMethodBinding methodBinding) { + ArrayDeque overloadedMethods = Stream.of(methodBinding.getDeclaringClass().getDeclaredMethods()) // + .filter(otherMethodBinding -> methodBinding.getName().equals(otherMethodBinding.getName())) // + .map(IMethodBinding::getJavaElement) // + .filter(IJavaElement::exists) + .collect(Collectors.toCollection(ArrayDeque::new)); + IJavaElement[] reorderedOverloadedMethods = new IJavaElement[overloadedMethods.size()]; + Iterator reverseIterator = overloadedMethods.descendingIterator(); + for (int i = 0; i < reorderedOverloadedMethods.length; i++) { + reorderedOverloadedMethods[i] = reverseIterator.next(); + } + return reorderedOverloadedMethods; + } + return new IJavaElement[] { importBinding.getJavaElement() }; + } else if (findTypeDeclaration(node) == null) { + IBinding binding = resolveBinding(node); + if (binding != null && !binding.isRecovered()) { + if (node instanceof SuperMethodInvocation && // on `super` + binding instanceof IMethodBinding methodBinding && + methodBinding.getDeclaringClass() instanceof ITypeBinding typeBinding && + typeBinding.getJavaElement() instanceof IType type) { + return new IJavaElement[] { type }; + } + if (binding instanceof IPackageBinding packageBinding + && trimmedText.length() > 0 + && !trimmedText.equals(packageBinding.getName()) + && packageBinding.getName().startsWith(trimmedText)) { + // resolved a too wide node for package name, restrict to selected name only + IJavaElement fragment = this.unit.getJavaProject().findPackageFragment(trimmedText); + if (fragment != null) { + return new IJavaElement[] { fragment }; + } + } + // workaround https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2177 + if (binding instanceof IVariableBinding variableBinding && + variableBinding.getDeclaringMethod() instanceof IMethodBinding declaringMethod && + declaringMethod.isCompactConstructor() && + Arrays.stream(declaringMethod.getParameterNames()).anyMatch(variableBinding.getName()::equals) && + declaringMethod.getDeclaringClass() instanceof ITypeBinding recordBinding && + recordBinding.isRecord() && + recordBinding.getJavaElement() instanceof IType recordType && + recordType.getField(variableBinding.getName()) instanceof SourceField field) { + // the parent must be the field and not the method + return new IJavaElement[] { new LocalVariable(field, + variableBinding.getName(), + 0, // must be 0 for subsequent call to LocalVariableLocator.matchLocalVariable() to work + field.getSourceRange().getOffset() + field.getSourceRange().getLength() - 1, + field.getNameRange().getOffset(), + field.getNameRange().getOffset() + field.getNameRange().getLength() - 1, + field.getTypeSignature(), + null, + field.getFlags(), + true) }; + } + if (binding instanceof ITypeBinding typeBinding && + typeBinding.isIntersectionType()) { + return Arrays.stream(typeBinding.getTypeBounds()) + .map(ITypeBinding::getJavaElement) + .filter(Objects::nonNull) + .toArray(IJavaElement[]::new); + } + IJavaElement element = binding.getJavaElement(); + if (element != null && (element instanceof IPackageFragment || element.exists())) { + return new IJavaElement[] { element }; + } + if (binding instanceof ITypeBinding typeBinding) { + if (this.unit.getJavaProject() != null) { + IType type = this.unit.getJavaProject().findType(typeBinding.getQualifiedName()); + if (type != null) { + return new IJavaElement[] { type }; + } + } + // fallback to calling index, inspired/copied from SelectionEngine + IJavaElement[] indexMatch = findTypeInIndex(typeBinding.getPackage() != null ? typeBinding.getPackage().getName() : null, typeBinding.getName()); + if (indexMatch.length > 0) { + return indexMatch; + } + } + if (binding instanceof IVariableBinding variableBinding && variableBinding.getDeclaringMethod() != null && variableBinding.getDeclaringMethod().isCompactConstructor()) { + // workaround for JavaSearchBugs15Tests.testBug558812_012 + if (variableBinding.getDeclaringMethod().getJavaElement() instanceof IMethod method) { + Optional parameter = Arrays.stream(method.getParameters()).filter(param -> Objects.equals(param.getElementName(), variableBinding.getName())).findAny(); + if (parameter.isPresent()) { + return new IJavaElement[] { parameter.get() }; + } + } + } + if (binding instanceof IMethodBinding methodBinding && + methodBinding.isSyntheticRecordMethod() && + methodBinding.getDeclaringClass().getJavaElement() instanceof IType recordType && + recordType.getField(methodBinding.getName()) instanceof IField field) { + return new IJavaElement[] { field }; + } + ASTNode bindingNode = currentAST.findDeclaringNode(binding); + if (bindingNode != null) { + IJavaElement parent = this.unit.getElementAt(bindingNode.getStartPosition()); + if (parent != null && bindingNode instanceof SingleVariableDeclaration variableDecl) { + return new IJavaElement[] { DOMToModelPopulator.toLocalVariable(variableDecl, (JavaElement)parent) }; + } + } + } + } + // fallback: crawl the children of this unit + IJavaElement currentElement = this.unit; + boolean newChildFound; + int finalOffset = offset; + int finalLength = length; + do { + newChildFound = false; + if (currentElement instanceof IParent parentElement) { + Optional candidate = Stream.of(parentElement.getChildren()) + .filter(ISourceReference.class::isInstance) + .map(ISourceReference.class::cast) + .filter(sourceRef -> { + try { + ISourceRange elementRange = sourceRef.getSourceRange(); + return elementRange != null + && elementRange.getOffset() >= 0 + && elementRange.getOffset() <= finalOffset + && elementRange.getOffset() + elementRange.getLength() >= finalOffset + finalLength; + } catch (JavaModelException e) { + return false; + } + }).map(IJavaElement.class::cast) + .findAny(); + if (candidate.isPresent()) { + newChildFound = true; + currentElement = candidate.get(); + } + } + } while (newChildFound); + if (currentElement instanceof JavaElement impl && + impl.getElementInfo() instanceof AnnotatableInfo annotable && + annotable.getNameSourceStart() >= 0 && + annotable.getNameSourceStart() <= offset && + annotable.getNameSourceEnd() + 1 /* end exclusive vs offset inclusive */ >= offset) { + return new IJavaElement[] { currentElement }; + } + if (insideComment) { + String toSearch = trimmedText.isBlank() ? findWord(offset) : trimmedText; + String resolved = ((List)currentAST.imports()).stream() + .map(org.eclipse.jdt.core.dom.ImportDeclaration::getName) + .map(Name::toString) + .filter(importedPackage -> importedPackage.endsWith(toSearch)) + .findAny() + .orElse(toSearch); + if (this.unit.getJavaProject().findType(resolved) instanceof IType type) { + return new IJavaElement[] { type }; + } + } + // failback to lookup search + ASTNode currentNode = node; + while (currentNode != null && !(currentNode instanceof Type)) { + currentNode = currentNode.getParent(); + } + if (currentNode instanceof Type parentType) { + if (this.unit.getJavaProject() != null) { + StringBuilder buffer = new StringBuilder(); + Util.getFullyQualifiedName(parentType, buffer); + IType type = this.unit.getJavaProject().findType(buffer.toString()); + if (type != null) { + return new IJavaElement[] { type }; + } + } + String packageName = parentType instanceof QualifiedType qType ? qType.getQualifier().toString() : + parentType instanceof SimpleType sType ? + sType.getName() instanceof QualifiedName qName ? qName.getQualifier().toString() : + null : + null; + String simpleName = parentType instanceof QualifiedType qType ? qType.getName().toString() : + parentType instanceof SimpleType sType ? + sType.getName() instanceof SimpleName sName ? sName.getIdentifier() : + sType.getName() instanceof QualifiedName qName ? qName.getName().toString() : + null : + null; + IJavaElement[] indexResult = findTypeInIndex(packageName, simpleName); + if (indexResult.length > 0) { + return indexResult; + } + } + // no good idea left + return new IJavaElement[0]; + } + + 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) { + ClassInstanceCreation newInstance = findConstructor(aName); + if (newInstance != null) { + var constructorBinding = newInstance.resolveConstructorBinding(); + if (constructorBinding != null) { + var constructorElement = constructorBinding.getJavaElement(); + if (constructorElement != null) { + boolean hasSource = true; + try { + hasSource = ((ISourceReference)constructorElement.getParent()).getSource() != null; + } catch (Exception e) { + hasSource = false; + } + if ((constructorBinding.getParameterTypes().length > 0 /*non-default*/ || + constructorElement instanceof SourceMethod || !hasSource)) { + return constructorBinding; + } + } else if (newInstance.resolveTypeBinding().isAnonymous()) { + // it's not in the anonymous class body, check for constructor decl in parent types + + ITypeBinding superclassBinding = newInstance.getType().resolveBinding(); + + while (superclassBinding != null) { + Optional potentialConstructor = Stream.of(superclassBinding.getDeclaredMethods()) // + .filter(methodBinding -> methodBinding.isConstructor() && matchSignatures(constructorBinding, methodBinding)) + .findFirst(); + if (potentialConstructor.isPresent()) { + IMethodBinding theConstructor = potentialConstructor.get(); + if (theConstructor.isDefaultConstructor()) { + return theConstructor.getDeclaringClass(); + } + return theConstructor; + } + superclassBinding = superclassBinding.getSuperclass(); + } + return null; + } + } + } + if (node.getParent() instanceof ExpressionMethodReference exprMethodReference && exprMethodReference.getName() == node) { + return resolveBinding(exprMethodReference); + } + if (node.getParent() instanceof TypeMethodReference typeMethodReference && typeMethodReference.getName() == node) { + return resolveBinding(typeMethodReference); + } + IBinding res = aName.resolveBinding(); + if (res != null) { + return res; + } + return resolveBinding(aName.getParent()); + } + if (node instanceof org.eclipse.jdt.core.dom.LambdaExpression lambda) { + return lambda.resolveMethodBinding(); + } + if (node instanceof ExpressionMethodReference methodRef) { + IMethodBinding methodBinding = methodRef.resolveMethodBinding(); + try { + if (methodBinding == null) { + return null; + } + IMethod methodModel = ((IMethod)methodBinding.getJavaElement()); + boolean allowExtraParam = true; + if ((methodModel.getFlags() & Flags.AccStatic) != 0) { + allowExtraParam = false; + if (methodRef.getExpression() instanceof ClassInstanceCreation) { + return null; + } + } + + // find the type that the method is bound to + ITypeBinding type = null; + ASTNode cursor = methodRef; + while (type == null && cursor != null) { + if (cursor.getParent() instanceof VariableDeclarationFragment declFragment) { + type = declFragment.resolveBinding().getType(); + } + else if (cursor.getParent() instanceof MethodInvocation methodInvocation) { + IMethodBinding methodInvocationBinding = methodInvocation.resolveMethodBinding(); + int index = methodInvocation.arguments().indexOf(cursor); + type = methodInvocationBinding.getParameterTypes()[index]; + } else { + cursor = cursor.getParent(); + } + } + + IMethodBinding boundMethod = type.getDeclaredMethods()[0]; + + if (boundMethod.getParameterTypes().length != methodBinding.getParameterTypes().length && (!allowExtraParam || boundMethod.getParameterTypes().length != methodBinding.getParameterTypes().length + 1)) { + return null; + } + } catch (JavaModelException e) { + return null; + } + return methodBinding; + } + if (node instanceof MethodReference methodRef) { + return methodRef.resolveMethodBinding(); + } + if (node instanceof org.eclipse.jdt.core.dom.TypeParameter typeParameter) { + return typeParameter.resolveBinding(); + } + if (node instanceof SuperConstructorInvocation superConstructor) { + return superConstructor.resolveConstructorBinding(); + } + if (node instanceof ConstructorInvocation constructor) { + return constructor.resolveConstructorBinding(); + } + if (node instanceof org.eclipse.jdt.core.dom.Annotation annotation) { + return annotation.resolveTypeBinding(); + } + if (node instanceof SuperMethodInvocation superMethod) { + return superMethod.resolveMethodBinding(); + } + return null; + } + + private static ClassInstanceCreation findConstructor(ASTNode node) { + while (node != null && !(node instanceof ClassInstanceCreation)) { + ASTNode parent = node.getParent(); + if ((parent instanceof SimpleType type && type.getName() == node) || + (parent instanceof ClassInstanceCreation constructor && constructor.getType() == node) || + (parent instanceof ParameterizedType parameterized && parameterized.getType() == node)) { + node = parent; + } else { + node = null; + } + } + return (ClassInstanceCreation)node; + } + + private static AbstractTypeDeclaration findTypeDeclaration(ASTNode node) { + ASTNode cursor = node; + while (cursor != null && (cursor instanceof Type || cursor instanceof Name)) { + cursor = cursor.getParent(); + } + if (cursor instanceof AbstractTypeDeclaration typeDecl && typeDecl.getName() == node) { + return typeDecl; + } + return null; + } + + private static org.eclipse.jdt.core.dom.ImportDeclaration findImportDeclaration(ASTNode node) { + while (node != null && !(node instanceof org.eclipse.jdt.core.dom.ImportDeclaration)) { + node = node.getParent(); + } + return (org.eclipse.jdt.core.dom.ImportDeclaration)node; + } + + private static boolean matchSignatures(IMethodBinding invocation, IMethodBinding declaration) { + if (declaration.getTypeParameters().length == 0) { + return invocation.isSubsignature(declaration); + } + if (invocation.getParameterTypes().length != declaration.getParameterTypes().length) { + return false; + } + for (int i = 0; i < invocation.getParameterTypes().length; i++) { + if (declaration.getParameterTypes()[i].isTypeVariable()) { + if (declaration.getParameterTypes()[i].getTypeBounds().length > 0) { + ITypeBinding[] bounds = declaration.getParameterTypes()[i].getTypeBounds(); + for (int j = 0; j < bounds.length; j++) { + if (!invocation.getParameterTypes()[i].isSubTypeCompatible(bounds[j])) { + return false; + } + } + } + } else if (!invocation.getParameterTypes()[i].isSubTypeCompatible(declaration.getParameterTypes()[i])) { + return false; + } + + } + return true; + } + + private IJavaElement[] findTypeInIndex(String packageName, String simpleName) throws JavaModelException { + List indexMatch = new ArrayList<>(); + TypeNameMatchRequestor requestor = new TypeNameMatchRequestor() { + @Override + public void acceptTypeNameMatch(org.eclipse.jdt.core.search.TypeNameMatch match) { + indexMatch.add(match.getType()); + } + }; + IJavaSearchScope scope = BasicSearchEngine.createJavaSearchScope(new IJavaProject[] { this.unit.getJavaProject() }); + new BasicSearchEngine(this.owner).searchAllTypeNames( + packageName != null ? packageName.toCharArray() : null, + SearchPattern.R_EXACT_MATCH, + simpleName.toCharArray(), + SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, + IJavaSearchConstants.TYPE, + scope, + new TypeNameMatchRequestorWrapper(requestor, scope), + IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, + new NullProgressMonitor()); + if (!indexMatch.isEmpty()) { + return indexMatch.toArray(IJavaElement[]::new); + } + scope = BasicSearchEngine.createWorkspaceScope(); + new BasicSearchEngine(this.owner).searchAllTypeNames( + packageName != null ? packageName.toCharArray() : null, + SearchPattern.R_EXACT_MATCH, + simpleName.toCharArray(), + SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, + IJavaSearchConstants.TYPE, + scope, + new TypeNameMatchRequestorWrapper(requestor, scope), + IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, + new NullProgressMonitor()); + if (!indexMatch.isEmpty()) { + return indexMatch.toArray(IJavaElement[]::new); + } + return new IJavaElement[0]; + } + + private String findWord(int offset) throws JavaModelException { + int start = offset; + String source = this.unit.getSource(); + while (start >= 0 && Character.isJavaIdentifierPart(source.charAt(start))) start--; + int end = offset + 1; + while (end < source.length() && Character.isJavaIdentifierPart(source.charAt(end))) end++; + return source.substring(start, end); + } +} 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 d9a4d44868e..178eac20114 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 @@ -17,13 +17,61 @@ package org.eclipse.jdt.internal.core; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.eclipse.core.resources.*; -import org.eclipse.core.runtime.*; -import org.eclipse.jdt.core.*; -import org.eclipse.jdt.core.compiler.*; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.PerformanceStats; +import org.eclipse.jdt.core.BufferChangedEvent; +import org.eclipse.jdt.core.CompletionRequestor; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IBuffer; +import org.eclipse.jdt.core.IBufferFactory; +import org.eclipse.jdt.core.ICodeAssist; +import org.eclipse.jdt.core.ICodeCompletionRequestor; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.ICompletionRequestor; +import org.eclipse.jdt.core.IImportContainer; +import org.eclipse.jdt.core.IImportDeclaration; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaModelStatusConstants; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMember; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IModuleDescription; +import org.eclipse.jdt.core.IOpenable; +import org.eclipse.jdt.core.IPackageDeclaration; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.IProblemRequestor; +import org.eclipse.jdt.core.ISourceManipulation; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ISourceReference; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeRoot; +import org.eclipse.jdt.core.IWorkingCopy; +import org.eclipse.jdt.core.JavaConventions; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.internal.codeassist.DOMCodeSelector; import org.eclipse.jdt.internal.compiler.IProblemFactory; import org.eclipse.jdt.internal.compiler.SourceElementParser; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; @@ -47,10 +95,12 @@ * @see ICompilationUnit */ public class CompilationUnit extends Openable implements ICompilationUnit, org.eclipse.jdt.internal.compiler.env.ICompilationUnit, SuffixConstants { + public static boolean DOM_BASED_OPERATIONS = Boolean.getBoolean(CompilationUnit.class.getSimpleName() + ".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 @@ -379,8 +429,38 @@ 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) { + return new DOMCodeSelector(this, workingCopyOwner).codeSelect(offset, length); + } else { + return super.codeSelect(this, offset, length, workingCopyOwner); + } +} + +public org.eclipse.jdt.core.dom.CompilationUnit getOrBuildAST(WorkingCopyOwner workingCopyOwner) throws JavaModelException { + if (this.ast != null) { + return this.ast; + } + Map options = getOptions(true); + ASTParser parser = ASTParser.newParser(new AST(options).apiLevel()); // go through AST constructor to convert options to apiLevel + parser.setWorkingCopyOwner(workingCopyOwner); + parser.setSource(this); + // greedily enable everything assuming the AST will be used extensively for edition + parser.setResolveBindings(true); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setCompilerOptions(options); + if (parser.createAST(null) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { + this.ast = newAST; + } + return this.ast; } + +@Override +public void bufferChanged(BufferChangedEvent event) { + this.ast = null; + super.bufferChanged(event); +} + /** * @see IWorkingCopy#commit(boolean, IProgressMonitor) * @deprecated @@ -1448,6 +1528,17 @@ public Map getCustomOptions() { if (this.owner != null) { try { Map customOptions = this.getCompilationUnitElementInfo().getCustomOptions(); + IJavaProject parentProject = getJavaProject(); + Map parentOptions = parentProject == null ? JavaCore.getOptions() : parentProject.getOptions(true); + if (JavaCore.ENABLED.equals(parentOptions.get(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES)) && + AST.newAST(parentOptions).apiLevel() < AST.getJLSLatest()) { + // Disable preview features for older Java releases as it causes the compiler to fail later + if (customOptions != null) { + customOptions.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.DISABLED); + } else { + customOptions = Map.of(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.DISABLED); + } + } return customOptions == null ? Collections.emptyMap() : customOptions; } catch (JavaModelException e) { // do nothing 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..737cdaf7be3 --- /dev/null +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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 org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.Comment; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +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 + */ +public class DOMToModelPopulator extends ASTVisitor { + + public static LocalVariable toLocalVariable(SingleVariableDeclaration parameter, JavaElement parent) { + return toLocalVariable(parameter, parent, parameter.getParent() instanceof MethodDeclaration); + } + + private static LocalVariable toLocalVariable(SingleVariableDeclaration parameter, JavaElement parent, boolean isParameter) { + return new LocalVariable(parent, + parameter.getName().getIdentifier(), + getStartConsideringLeadingComments(parameter), + parameter.getStartPosition() + parameter.getLength() - 1, + parameter.getName().getStartPosition(), + parameter.getName().getStartPosition() + parameter.getName().getLength() - 1, + Util.getSignature(parameter.getType()), + null, // should be populated while navigating children + toModelFlags(parameter.getModifiers(), false), + isParameter); + } + + private static int getStartConsideringLeadingComments(ASTNode node) { + int start = node.getStartPosition(); + var unit = domUnit(node); + int index = unit.firstLeadingCommentIndex(node); + if (index >= 0 && index <= unit.getCommentList().size()) { + Comment comment = (Comment)unit.getCommentList().get(index); + start = comment.getStartPosition(); + } + return start; + } + + 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; + } + + private static int toModelFlags(int domModifiers, boolean isDeprecated) { + int res = 0; + if (Modifier.isAbstract(domModifiers)) res |= Flags.AccAbstract; + if (Modifier.isDefault(domModifiers)) res |= Flags.AccDefaultMethod; + if (Modifier.isFinal(domModifiers)) res |= Flags.AccFinal; + if (Modifier.isNative(domModifiers)) res |= Flags.AccNative; + if (Modifier.isNonSealed(domModifiers)) res |= Flags.AccNonSealed; + if (Modifier.isPrivate(domModifiers)) res |= Flags.AccPrivate; + if (Modifier.isProtected(domModifiers)) res |= Flags.AccProtected; + if (Modifier.isPublic(domModifiers)) res |= Flags.AccPublic; + if (Modifier.isSealed(domModifiers)) res |= Flags.AccSealed; + if (Modifier.isStatic(domModifiers)) res |= Flags.AccStatic; + if (Modifier.isStrictfp(domModifiers)) res |= Flags.AccStrictfp; + if (Modifier.isSynchronized(domModifiers)) res |= Flags.AccSynchronized; + if (Modifier.isTransient(domModifiers)) res |= Flags.AccTransient; + if (Modifier.isVolatile(domModifiers)) res |= Flags.AccVolatile; + if (isDeprecated) res |= Flags.AccDeprecated; + return res; + } + +} diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/Util.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/Util.java index ed7cb186e8f..dd786d648d4 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/Util.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/Util.java @@ -1277,7 +1277,7 @@ public static String getDeclaringTypeSignature(String key) { /* * Appends to the given buffer the fully qualified name (as it appears in the source) of the given type */ - private static void getFullyQualifiedName(Type type, StringBuilder buffer) { + public static void getFullyQualifiedName(Type type, StringBuilder buffer) { switch (type.getNodeType()) { case ASTNode.ARRAY_TYPE: ArrayType arrayType = (ArrayType) type;