diff --git a/java-frontend/pom.xml b/java-frontend/pom.xml index 6f3a4b1cd43..6e3de0905fb 100644 --- a/java-frontend/pom.xml +++ b/java-frontend/pom.xml @@ -18,12 +18,6 @@ - - ${project.groupId} - jdt-package - 1.9.0.1840 - - org.sonarsource.api.plugin sonar-plugin-api diff --git a/java-frontend/src/main/java/org/eclipse/jdt/core/dom/ASTUtils.java b/java-frontend/src/main/java/org/eclipse/jdt/core/dom/ASTUtils.java deleted file mode 100644 index 80bc65622b0..00000000000 --- a/java-frontend/src/main/java/org/eclipse/jdt/core/dom/ASTUtils.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * SonarQube Java - * Copyright (C) SonarSource Sàrl - * mailto:info AT sonarsource DOT com - * - * You can redistribute and/or modify this program under the terms of - * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the Sonar Source-Available License for more details. - * - * You should have received a copy of the Sonar Source-Available License - * along with this program; if not, see https://sonarsource.com/license/ssal/ - */ -package org.eclipse.jdt.core.dom; - -import java.util.Objects; -import javax.annotation.Nullable; -import org.eclipse.jdt.core.compiler.CharOperation; -import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation; -import org.eclipse.jdt.internal.compiler.env.IBinaryType; -import org.eclipse.jdt.internal.compiler.env.INameEnvironment; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; -import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; -import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding; -import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; -import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; -import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class ASTUtils { - - private static final Logger LOG = LoggerFactory.getLogger(ASTUtils.class); - - private static final IAnnotationBinding[] NO_ANNOTATIONS = new IAnnotationBinding[0]; - - private ASTUtils() { - } - - public static void mayTolerateMissingType(AST ast) { - ast.getBindingResolver().lookupEnvironment().mayTolerateMissingType = true; - } - - public static Runnable getEnvironmentCleaner(AST ast) { - return new EnvironmentCleaner(ast.getBindingResolver().lookupEnvironment().nameEnvironment); - } - - @Nullable - public static ITypeBinding resolveType(AST ast, String name) { - try { - BindingResolver bindingResolver = ast.getBindingResolver(); - ReferenceBinding referenceBinding = bindingResolver - .lookupEnvironment() - .getType(CharOperation.splitOn('.', name.toCharArray())); - return bindingResolver.getTypeBinding(referenceBinding); - } catch (Exception e) { - // exception on ECJ side when trying to resolve a Type, recover on null type - LOG.error("ECJ Unable to resolve type " + name, e); - return null; - } - } - - public static IAnnotationBinding[] resolvePackageAnnotations(AST ast, String packageName) { - // See org.eclipse.jdt.core.dom.PackageBinding#getAnnotations() - BindingResolver bindingResolver = ast.getBindingResolver(); - LookupEnvironment lookupEnvironment = bindingResolver.lookupEnvironment(); - NameEnvironmentAnswer answer = lookupEnvironment.nameEnvironment.findType( - TypeConstants.PACKAGE_INFO_NAME, - CharOperation.splitOn('.', packageName.toCharArray()) - ); - if (answer == null) { - return NO_ANNOTATIONS; - } - IBinaryType type = answer.getBinaryType(); - if (type == null) { - // Can happen for instance with ant, as ant only generates 'package-info.class' - // when there is annotations in the package-info.java file. - return NO_ANNOTATIONS; - } - IBinaryAnnotation[] binaryAnnotations = type.getAnnotations(); - AnnotationBinding[] binaryInstances = - BinaryTypeBinding.createAnnotations(binaryAnnotations, lookupEnvironment, type.getMissingTypeNames()); - AnnotationBinding[] allInstances = - AnnotationBinding.addStandardAnnotations(binaryInstances, type.getTagBits(), lookupEnvironment); - - IAnnotationBinding[] domInstances = new IAnnotationBinding[allInstances.length]; - for (int i = 0; i < allInstances.length; i++) { - // FIXME can be null if annotation can not be resolved e.g. due to incomplete classpath - domInstances[i] = bindingResolver.getAnnotationInstance(allInstances[i]); - } - return domInstances; - } - - public static String signature(IMethodBinding methodBinding) { - char[] signature = ((MethodBinding) methodBinding).binding.signature(); - return new String(signature); - } - - private static class EnvironmentCleaner implements Runnable { - private final INameEnvironment env; - - private EnvironmentCleaner(INameEnvironment env) { - this.env = env; - } - - @Override - public void run() { - env.cleanup(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - EnvironmentCleaner that = (EnvironmentCleaner) o; - return Objects.equals(env, that.env); - } - - @Override - public int hashCode() { - return Objects.hash(env); - } - } - -} diff --git a/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java b/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java index 74e3e81452c..dd1c71e52a2 100644 --- a/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java +++ b/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java @@ -19,12 +19,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; -import org.eclipse.core.runtime.IProgressMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.java.annotations.VisibleForTesting; -public class ProgressMonitor implements IProgressMonitor, Runnable { +public class ProgressMonitor implements Runnable { private final Logger logger; private final long period; @@ -84,7 +83,6 @@ public void run() { } } - @Override public void beginTask(String name, int totalWork) { if (totalWork <= 0) { unknownTotalWork = true; @@ -96,7 +94,6 @@ public void beginTask(String name, int totalWork) { thread.start(); } - @Override public void done() { if (success && analysisProgress.isLastBatch()) { log("100% analyzed"); @@ -107,7 +104,6 @@ public void done() { join(); } - @Override public boolean isCanceled() { if (isCanceled.getAsBoolean()) { log("Batch processing: Cancelled!"); @@ -116,7 +112,6 @@ public boolean isCanceled() { return false; } - @Override public void setCanceled(boolean value) { // do nothing } @@ -129,7 +124,6 @@ private void join() { } } - @Override public void worked(int work) { processedWork += work; if (processedWork == totalWork) { @@ -137,17 +131,14 @@ public void worked(int work) { } } - @Override public void internalWorked(double work) { // do nothing } - @Override public void setTaskName(String name) { // do nothing } - @Override public void subTask(String name) { // do nothing } diff --git a/java-frontend/src/main/java/org/sonar/java/model/AbstractTypedTree.java b/java-frontend/src/main/java/org/sonar/java/model/AbstractTypedTree.java index f2d6def6b5e..20c8e90fc89 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/AbstractTypedTree.java +++ b/java-frontend/src/main/java/org/sonar/java/model/AbstractTypedTree.java @@ -16,23 +16,15 @@ */ package org.sonar.java.model; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.plugins.java.api.semantic.Type; -import javax.annotation.Nullable; - /** * This class is intended for internal use during semantic analysis and should not be used in checks. */ public abstract class AbstractTypedTree extends JavaTree { - @Nullable - public ITypeBinding typeBinding; - public Type symbolType() { - return typeBinding != null - ? root.sema.type(typeBinding) - : Type.UNKNOWN; + return Type.UNKNOWN; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java index 3f15b5f9a6b..e9cff7cca58 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java @@ -16,202 +16,87 @@ */ package org.sonar.java.model; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; +import java.util.Collections; import java.util.List; -import java.util.Set; -import java.util.function.Predicate; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ASTUtils; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.Modifier; -import org.sonar.java.annotations.VisibleForTesting; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.MethodTree; -import org.sonar.plugins.java.api.tree.VariableTree; final class JMethodSymbol extends JSymbol implements Symbol.MethodSymbol { - /** - * Cache for {@link #parameterTypes()}. - */ - private List parameterTypes; - - private List parameters; - - /** - * Cache for {@link #returnType()}. - */ - private TypeSymbol returnType; - - /** - * Cache for {@link #thrownTypes()}. - */ - private List thrownTypes; - - /** - * Cache for {@link #overriddenSymbols()}. - */ - private List overriddenSymbols; - - private final String signature; - - JMethodSymbol(JSema sema, IMethodBinding methodBinding) { - super(sema, methodBinding); - this.signature = methodBinding().getDeclaringClass().getBinaryName() - + "#" + name() - + ASTUtils.signature(methodBinding().getMethodDeclaration()); - } - - IMethodBinding methodBinding() { - return (IMethodBinding) binding; + JMethodSymbol(JSema sema) { + super(sema, Kind.METHOD); } public boolean isLambda() { - return methodBinding().getDeclaringMember() != null; + return false; } @Override public List parameterTypes() { - if (parameterTypes == null) { - parameterTypes = sema.types(methodBinding().getParameterTypes()); - } - return parameterTypes; + return Collections.emptyList(); } @Override public List declarationParameters() { - if (parameters == null) { - MethodTree declaration = declaration(); - if (declaration != null && !isCompactConstructor(declaration)) { - parameters = declaration.parameters().stream().map(VariableTree::symbol).toList(); - } else { - parameters = new ArrayList<>(); - IMethodBinding methodBinding = methodBinding(); - ITypeBinding[] parameterTypeBindings = methodBinding.getParameterTypes(); - for (int i = 0; i < parameterTypeBindings.length; i++) { - parameters.add(new JVariableSymbol.ParameterPlaceholderSymbol(i, sema, methodBinding.getMethodDeclaration(), parameterTypeBindings[i])); - } - } - } - return parameters; - } - - private static boolean isCompactConstructor(MethodTree methodTree) { - return methodTree.closeParenToken() == null; + return Collections.emptyList(); } - /** - * @since 6.0 returns void type for constructors instead of {@code null} - */ @Override public TypeSymbol returnType() { - if (returnType == null) { - ITypeBinding methodBindingReturnType = methodBinding().getReturnType(); - // In rare circumstances, when the semantic information is incomplete, methodBindingReturnType can be null. - if (methodBindingReturnType == null) { - return TypeSymbol.UNKNOWN_TYPE; - } - this.returnType = sema.typeSymbol(methodBindingReturnType); - } - return returnType; + return TypeSymbol.UNKNOWN_TYPE; } @Override public List thrownTypes() { - if (thrownTypes == null) { - thrownTypes = sema.types(methodBinding().getExceptionTypes()); - } - return thrownTypes; + return Collections.emptyList(); } @Override public List overriddenSymbols() { - if (overriddenSymbols == null) { - overriddenSymbols = findOverriddenSymbols(); - } - return overriddenSymbols; - } - - private List findOverriddenSymbols() { - Set results = new LinkedHashSet<>(); - IMethodBinding methodBinding = methodBinding(); - findOverridesInParentTypes(results, methodBinding::overrides, methodBinding.getDeclaringClass()); - return new ArrayList<>(results); - } - - @VisibleForTesting - void findOverridesInParentTypes(Collection accumulator, Predicate overridesCondition, ITypeBinding type) { - if (type.isInterface()) { - // check Object for interfaces forcing overrides from Object - findOverridesInTypes(accumulator, overridesCondition, sema.resolveType("java.lang.Object")); - } else if (!"java.lang.Object".equals(type.getQualifiedName())) { - findOverridesInTypes(accumulator, overridesCondition, type.getSuperclass()); - } - findOverridesInTypes(accumulator, overridesCondition, type.getInterfaces()); - } - - private void findOverridesInTypes(Collection accumulator, Predicate overridesCondition, ITypeBinding... types) { - for (ITypeBinding type : types) { - if (type != null) { - for (IMethodBinding declaredMethod : type.getDeclaredMethods()) { - if (overridesCondition.test(declaredMethod)) { - accumulator.add(sema.methodSymbol(declaredMethod)); - break; - } - } - findOverridesInParentTypes(accumulator, overridesCondition, type); - } - } + return Collections.emptyList(); } @Override public String signature() { - return signature; + return ""; } @Override public boolean isOverridable() { - return !isUnknown() && !(isPrivate() || isStatic() || isFinal() || owner().isFinal()); + return false; } @Override public boolean isParametrizedMethod() { - return !isUnknown() && (methodBinding().isParameterizedMethod() || methodBinding().isGenericMethod()); + return false; } @Override public boolean isDefaultMethod() { - return !isUnknown() && Modifier.isDefault(binding.getModifiers()); + return false; } @Override public boolean isSynchronizedMethod() { - return !isUnknown() && Modifier.isSynchronized(binding.getModifiers()); + return false; } @Override public boolean isVarArgsMethod() { - return !isUnknown() && methodBinding().isVarargs(); + return false; } @Nullable @Override public MethodTree declaration() { - return (MethodTree) super.declaration(); + return null; } @Override public boolean isNativeMethod() { - return !isUnknown() && Modifier.isNative(binding.getModifiers()); + return false; } - /** This is for debugging and doesn't follow a guaranteed format. */ - @Override - public String toString() { - return binding.getKey(); - } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java index 1f7680c909e..b3fc526a4a9 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java @@ -16,12 +16,10 @@ */ package org.sonar.java.model; -import org.eclipse.jdt.core.dom.IPackageBinding; - final class JPackageSymbol extends JSymbol { - JPackageSymbol(JSema sema, IPackageBinding binding) { - super(sema, binding); + JPackageSymbol(JSema sema) { + super(sema, Kind.PACKAGE); } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JParser.java b/java-frontend/src/main/java/org/sonar/java/model/JParser.java index e58aeb7f024..874b96a6e70 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JParser.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JParser.java @@ -16,2890 +16,32 @@ */ package org.sonar.java.model; -import com.sonar.sslr.api.RecognitionException; -import java.util.ArrayList; import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import javax.annotation.Nullable; -import org.eclipse.jdt.core.compiler.IProblem; -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.ASTParser; -import org.eclipse.jdt.core.dom.ASTUtils; -import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; -import org.eclipse.jdt.core.dom.Annotation; -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.ArrayAccess; -import org.eclipse.jdt.core.dom.ArrayCreation; -import org.eclipse.jdt.core.dom.ArrayInitializer; -import org.eclipse.jdt.core.dom.ArrayType; -import org.eclipse.jdt.core.dom.AssertStatement; -import org.eclipse.jdt.core.dom.Assignment; -import org.eclipse.jdt.core.dom.Block; -import org.eclipse.jdt.core.dom.BodyDeclaration; -import org.eclipse.jdt.core.dom.BooleanLiteral; -import org.eclipse.jdt.core.dom.BreakStatement; -import org.eclipse.jdt.core.dom.CastExpression; -import org.eclipse.jdt.core.dom.CatchClause; -import org.eclipse.jdt.core.dom.CharacterLiteral; -import org.eclipse.jdt.core.dom.ClassInstanceCreation; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.ConditionalExpression; -import org.eclipse.jdt.core.dom.ConstructorInvocation; -import org.eclipse.jdt.core.dom.ContinueStatement; -import org.eclipse.jdt.core.dom.CreationReference; -import org.eclipse.jdt.core.dom.Dimension; -import org.eclipse.jdt.core.dom.DoStatement; -import org.eclipse.jdt.core.dom.EmptyStatement; -import org.eclipse.jdt.core.dom.EnhancedForStatement; -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; -import org.eclipse.jdt.core.dom.ExpressionMethodReference; -import org.eclipse.jdt.core.dom.ExpressionStatement; -import org.eclipse.jdt.core.dom.FieldAccess; -import org.eclipse.jdt.core.dom.FieldDeclaration; -import org.eclipse.jdt.core.dom.ForStatement; -import org.eclipse.jdt.core.dom.GuardedPattern; -import org.eclipse.jdt.core.dom.IBinding; -import org.eclipse.jdt.core.dom.IExtendedModifier; -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.IfStatement; -import org.eclipse.jdt.core.dom.ImportDeclaration; -import org.eclipse.jdt.core.dom.InfixExpression; -import org.eclipse.jdt.core.dom.Initializer; -import org.eclipse.jdt.core.dom.InstanceofExpression; -import org.eclipse.jdt.core.dom.IntersectionType; -import org.eclipse.jdt.core.dom.LabeledStatement; -import org.eclipse.jdt.core.dom.LambdaExpression; -import org.eclipse.jdt.core.dom.MemberValuePair; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.MethodInvocation; -import org.eclipse.jdt.core.dom.ModuleDeclaration; -import org.eclipse.jdt.core.dom.ModuleDirective; -import org.eclipse.jdt.core.dom.ModuleModifier; -import org.eclipse.jdt.core.dom.Name; -import org.eclipse.jdt.core.dom.NameQualifiedType; -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.ParameterizedType; -import org.eclipse.jdt.core.dom.ParenthesizedExpression; -import org.eclipse.jdt.core.dom.Pattern; -import org.eclipse.jdt.core.dom.PatternInstanceofExpression; -import org.eclipse.jdt.core.dom.PostfixExpression; -import org.eclipse.jdt.core.dom.PrefixExpression; -import org.eclipse.jdt.core.dom.PrimitiveType; -import org.eclipse.jdt.core.dom.ProvidesDirective; -import org.eclipse.jdt.core.dom.QualifiedName; -import org.eclipse.jdt.core.dom.QualifiedType; -import org.eclipse.jdt.core.dom.RecordDeclaration; -import org.eclipse.jdt.core.dom.RecordPattern; -import org.eclipse.jdt.core.dom.RequiresDirective; -import org.eclipse.jdt.core.dom.ReturnStatement; -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.Statement; -import org.eclipse.jdt.core.dom.StringLiteral; -import org.eclipse.jdt.core.dom.SuperConstructorInvocation; -import org.eclipse.jdt.core.dom.SuperFieldAccess; -import org.eclipse.jdt.core.dom.SuperMethodInvocation; -import org.eclipse.jdt.core.dom.SuperMethodReference; -import org.eclipse.jdt.core.dom.SwitchCase; -import org.eclipse.jdt.core.dom.SwitchExpression; -import org.eclipse.jdt.core.dom.SwitchStatement; -import org.eclipse.jdt.core.dom.SynchronizedStatement; -import org.eclipse.jdt.core.dom.TextBlock; -import org.eclipse.jdt.core.dom.ThisExpression; -import org.eclipse.jdt.core.dom.ThrowStatement; -import org.eclipse.jdt.core.dom.TryStatement; -import org.eclipse.jdt.core.dom.Type; -import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.jdt.core.dom.TypeDeclarationStatement; -import org.eclipse.jdt.core.dom.TypeLiteral; -import org.eclipse.jdt.core.dom.TypeMethodReference; -import org.eclipse.jdt.core.dom.TypeParameter; -import org.eclipse.jdt.core.dom.TypePattern; -import org.eclipse.jdt.core.dom.UnionType; -import org.eclipse.jdt.core.dom.UsesDirective; -import org.eclipse.jdt.core.dom.VariableDeclaration; -import org.eclipse.jdt.core.dom.VariableDeclarationExpression; -import org.eclipse.jdt.core.dom.VariableDeclarationFragment; -import org.eclipse.jdt.core.dom.VariableDeclarationStatement; -import org.eclipse.jdt.core.dom.WhileStatement; -import org.eclipse.jdt.core.dom.WildcardType; -import org.eclipse.jdt.core.dom.YieldStatement; -import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; -import org.eclipse.jdt.internal.compiler.parser.Scanner; -import org.eclipse.jdt.internal.compiler.parser.TerminalToken; -import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions; -import org.eclipse.jdt.internal.formatter.Token; -import org.eclipse.jdt.internal.formatter.TokenManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.java.annotations.VisibleForTesting; -import org.sonar.java.ast.parser.ArgumentListTreeImpl; -import org.sonar.java.ast.parser.FormalParametersListTreeImpl; -import org.sonar.java.ast.parser.InitializerListTreeImpl; -import org.sonar.java.ast.parser.ModuleNameListTreeImpl; -import org.sonar.java.ast.parser.ModuleNameTreeImpl; -import org.sonar.java.ast.parser.QualifiedIdentifierListTreeImpl; -import org.sonar.java.ast.parser.ResourceListTreeImpl; -import org.sonar.java.ast.parser.StatementListTreeImpl; -import org.sonar.java.ast.parser.TypeParameterListTreeImpl; -import org.sonar.java.model.declaration.AnnotationTreeImpl; -import org.sonar.java.model.declaration.ClassTreeImpl; -import org.sonar.java.model.declaration.EnumConstantTreeImpl; -import org.sonar.java.model.declaration.ExportsDirectiveTreeImpl; -import org.sonar.java.model.declaration.MethodTreeImpl; -import org.sonar.java.model.declaration.ModifierKeywordTreeImpl; -import org.sonar.java.model.declaration.ModifiersTreeImpl; -import org.sonar.java.model.declaration.ModuleDeclarationTreeImpl; -import org.sonar.java.model.declaration.OpensDirectiveTreeImpl; -import org.sonar.java.model.declaration.ProvidesDirectiveTreeImpl; -import org.sonar.java.model.declaration.RequiresDirectiveTreeImpl; -import org.sonar.java.model.declaration.UsesDirectiveTreeImpl; -import org.sonar.java.model.declaration.VariableTreeImpl; -import org.sonar.java.model.expression.ArrayAccessExpressionTreeImpl; -import org.sonar.java.model.expression.AssignmentExpressionTreeImpl; -import org.sonar.java.model.expression.BinaryExpressionTreeImpl; -import org.sonar.java.model.expression.ConditionalExpressionTreeImpl; -import org.sonar.java.model.expression.IdentifierTreeImpl; -import org.sonar.java.model.expression.InstanceOfTreeImpl; -import org.sonar.java.model.expression.InternalPostfixUnaryExpression; -import org.sonar.java.model.expression.InternalPrefixUnaryExpression; -import org.sonar.java.model.expression.LambdaExpressionTreeImpl; -import org.sonar.java.model.expression.LiteralTreeImpl; -import org.sonar.java.model.expression.MemberSelectExpressionTreeImpl; -import org.sonar.java.model.expression.MethodInvocationTreeImpl; -import org.sonar.java.model.expression.MethodReferenceTreeImpl; -import org.sonar.java.model.expression.NewArrayTreeImpl; -import org.sonar.java.model.expression.NewClassTreeImpl; -import org.sonar.java.model.expression.ParenthesizedTreeImpl; -import org.sonar.java.model.expression.TypeArgumentListTreeImpl; -import org.sonar.java.model.expression.TypeCastExpressionTreeImpl; -import org.sonar.java.model.expression.VarTypeTreeImpl; -import org.sonar.java.model.pattern.DefaultPatternTreeImpl; -import org.sonar.java.model.pattern.GuardedPatternTreeImpl; -import org.sonar.java.model.pattern.NullPatternTreeImpl; -import org.sonar.java.model.pattern.RecordPatternTreeImpl; -import org.sonar.java.model.pattern.TypePatternTreeImpl; -import org.sonar.java.model.statement.AssertStatementTreeImpl; -import org.sonar.java.model.statement.BlockTreeImpl; -import org.sonar.java.model.statement.BreakStatementTreeImpl; -import org.sonar.java.model.statement.CaseGroupTreeImpl; -import org.sonar.java.model.statement.CaseLabelTreeImpl; -import org.sonar.java.model.statement.CatchTreeImpl; -import org.sonar.java.model.statement.ContinueStatementTreeImpl; -import org.sonar.java.model.statement.DoWhileStatementTreeImpl; -import org.sonar.java.model.statement.EmptyStatementTreeImpl; -import org.sonar.java.model.statement.ExpressionStatementTreeImpl; -import org.sonar.java.model.statement.ForEachStatementImpl; -import org.sonar.java.model.statement.ForStatementTreeImpl; -import org.sonar.java.model.statement.IfStatementTreeImpl; -import org.sonar.java.model.statement.LabeledStatementTreeImpl; -import org.sonar.java.model.statement.ReturnStatementTreeImpl; -import org.sonar.java.model.statement.StaticInitializerTreeImpl; -import org.sonar.java.model.statement.SwitchExpressionTreeImpl; -import org.sonar.java.model.statement.SwitchStatementTreeImpl; -import org.sonar.java.model.statement.SynchronizedStatementTreeImpl; -import org.sonar.java.model.statement.ThrowStatementTreeImpl; -import org.sonar.java.model.statement.TryStatementTreeImpl; -import org.sonar.java.model.statement.WhileStatementTreeImpl; -import org.sonar.java.model.statement.YieldStatementTreeImpl; -import org.sonar.plugins.java.api.tree.AnnotationTree; -import org.sonar.plugins.java.api.tree.ArrayDimensionTree; -import org.sonar.plugins.java.api.tree.ArrayTypeTree; -import org.sonar.plugins.java.api.tree.CatchTree; -import org.sonar.plugins.java.api.tree.ExpressionTree; -import org.sonar.plugins.java.api.tree.IdentifierTree; -import org.sonar.plugins.java.api.tree.ImportClauseTree; -import org.sonar.plugins.java.api.tree.InferedTypeTree; -import org.sonar.plugins.java.api.tree.ListTree; -import org.sonar.plugins.java.api.tree.Modifier; -import org.sonar.plugins.java.api.tree.ModifierTree; -import org.sonar.plugins.java.api.tree.ModuleDeclarationTree; -import org.sonar.plugins.java.api.tree.ModuleDirectiveTree; -import org.sonar.plugins.java.api.tree.PackageDeclarationTree; -import org.sonar.plugins.java.api.tree.PatternTree; -import org.sonar.plugins.java.api.tree.StatementTree; -import org.sonar.plugins.java.api.tree.SyntaxToken; -import org.sonar.plugins.java.api.tree.SyntaxTrivia; -import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; -import org.sonar.plugins.java.api.tree.Tree; -import org.sonar.plugins.java.api.tree.TypeParameterTree; -import org.sonar.plugins.java.api.tree.TypeTree; -import org.sonar.plugins.java.api.tree.VariableTree; -@SuppressWarnings({"rawtypes", "unchecked"}) -public class JParser { +public final class JParser { - private static final Logger LOG = LoggerFactory.getLogger(JParser.class); - - private static final Set WRONGLY_CATEGORIZED_AS_SYNTAX_ERROR = Set.of( - // Accept missing default clause, it may be due to missing semantic information of the switch expression, - // in this case, an enum fully covered with the switch cases will be seen as something that is not an enum - // when it is unknown, and the parser will wrongly consider the missing default clause as a syntax error. - IProblem.SwitchExpressionsYieldMissingDefaultCase, - // Accept missing default clause, it may be due the switch expression being an enum from a wrong dependency. - // In this case, the parser will wrongly consider the missing default clause as a syntax error. - IProblem.SwitchExpressionsYieldMissingEnumConstantCase - ); - - private static final Predicate IS_SYNTAX_ERROR = error -> ((error.getID() & IProblem.Syntax) != 0) && - !WRONGLY_CATEGORIZED_AS_SYNTAX_ERROR.contains(error.getID()); - - private static final Predicate IS_UNDEFINED_TYPE_ERROR = error -> (error.getID() & IProblem.UndefinedType) != 0; - - /** - * @param unitName see {@link ASTParser#setUnitName(String)} - * @throws RecognitionException in case of syntax errors - */ - public static JavaTree.CompilationUnitTreeImpl parse(ASTParser astParser, String version, String unitName, String source) { - astParser.setUnitName(unitName); - astParser.setSource(source.toCharArray()); - - CompilationUnit astNode; - try { - astNode = (CompilationUnit) astParser.createAST(null); - } catch (Exception e) { - LOG.error("ECJ: Unable to parse file", e); - throw new RecognitionException(-1, "ECJ: Unable to parse file.", e); - } - - return convert(version, unitName, source, astNode); - } - - static JavaTree.CompilationUnitTreeImpl convert(String version, String unitName, String source, CompilationUnit astNode) { - List errors = Stream.of(astNode.getProblems()).filter(IProblem::isError).toList(); - Optional possibleSyntaxError = errors.stream().filter(IS_SYNTAX_ERROR).findFirst(); - LineColumnConverter lineColumnConverter = new LineColumnConverter(source); - if (possibleSyntaxError.isPresent()) { - IProblem syntaxError = possibleSyntaxError.get(); - LineColumnConverter.Pos pos = lineColumnConverter.toPos(syntaxError.getSourceStart()); - String message = String.format("Parse error at line %d column %d: %s", pos.line(), pos.columnOffset() + 1, syntaxError.getMessage()); - // interrupt parsing - throw new RecognitionException(pos.line(), message); - } - - Set undefinedTypes = errors.stream() - .filter(IS_UNDEFINED_TYPE_ERROR) - .map(i -> new JProblem( - i.getMessage(), - (i.getID() & IProblem.PreviewFeatureUsed) == 0 ? JProblem.Type.UNDEFINED_TYPE : JProblem.Type.PREVIEW_FEATURE_USED)) - .collect(Collectors.toSet()); - - JParser converter = new JParser(); - converter.sema = new JSema(astNode.getAST()); - converter.sema.undefinedTypes.addAll(undefinedTypes); - converter.compilationUnit = astNode; - converter.tokenManager = createTokenManager(version, unitName, source); - converter.lineColumnConverter = lineColumnConverter; - - JavaTree.CompilationUnitTreeImpl tree = converter.convertCompilationUnit(astNode); - tree.sema = converter.sema; - JWarning.Mapper.warningsFor(astNode, converter.lineColumnConverter).mappedInto(tree); - - ASTUtils.mayTolerateMissingType(astNode.getAST()); - - setParents(tree); - return tree; - } - - @VisibleForTesting - static TokenManager createTokenManager(String version, String unitName, String source) { - return new TokenManager(lex(version, unitName, source.toCharArray()), source, new DefaultCodeFormatterOptions(new HashMap<>())); - } - - private static void setParents(Tree node) { - Iterator childrenIterator = iteratorFor(node); - while (childrenIterator.hasNext()) { - Tree child = childrenIterator.next(); - ((JavaTree) child).setParent(node); - setParents(child); - } - } - - private static Iterator iteratorFor(Tree node) { - if (node.kind() == Tree.Kind.INFERED_TYPE || node.kind() == Tree.Kind.TOKEN) { - // getChildren throws exception in this case - return Collections.emptyIterator(); - } - return ((JavaTree) node).getChildren().iterator(); - } - - @VisibleForTesting - static List lex(String version, String unitName, char[] sourceChars) { - List tokens = new ArrayList<>(); - Scanner scanner = new Scanner( - true, - false, - false, - CompilerOptions.versionToJdkLevel(version), - null, - null, - false - ); - scanner.fakeInModule = "module-info.java".equals(unitName); - scanner.setSource(sourceChars); - while (true) { - try { - TerminalToken tokenType = scanner.getNextToken(); - Token token = Token.fromCurrent(scanner, tokenType); - tokens.add(token); - if (tokenType == TerminalToken.TokenNameEOF) { - break; - } - } catch (InvalidInputException e) { - throw new IllegalStateException(e); - } - } - return tokens; - } - - private CompilationUnit compilationUnit; - - private TokenManager tokenManager; - private LineColumnConverter lineColumnConverter; - - private JSema sema; - - private final Deque labels = new LinkedList<>(); - - private void declaration(@Nullable IBinding binding, Tree node) { - if (binding == null) { - return; - } - sema.declarations.put(binding, node); - } - - private void usage(@Nullable IBinding binding, IdentifierTree node) { - if (binding == null) { - return; - } - binding = JSema.declarationBinding(binding); - sema.usages.computeIfAbsent(binding, k -> new ArrayList<>()).add(node); - } - - private void usageLabel(@Nullable IdentifierTreeImpl node) { - if (node == null) { - return; - } - labels.stream() - .filter(symbol -> symbol.name().equals(node.name())) - .findFirst() - .ifPresent(labelSymbol -> { - labelSymbol.usages.add(node); - node.labelSymbol = labelSymbol; - }); - } - - private int firstTokenIndexAfter(ASTNode e) { - int index = tokenManager.firstIndexAfter(e, ANY_TOKEN); - while (isComment(tokenManager.get(index))) { - index++; - } - return index; - } - - /** - * @param tokenType {@link TerminalToken} - */ - private int nextTokenIndex(int tokenIndex, TerminalToken tokenType) { - assert tokenType != ANY_TOKEN; - do { - tokenIndex += 1; - } while (tokenManager.get(tokenIndex).tokenType != tokenType); - return tokenIndex; - } - - /** - * @param tokenType {@link TerminalToken} - */ - private InternalSyntaxToken firstTokenBefore(ASTNode e, TerminalToken tokenType) { - return createSyntaxToken(tokenManager.firstIndexBefore(e, tokenType)); - } - - /** - * @param tokenType {@link TerminalToken} - */ - private InternalSyntaxToken firstTokenAfter(ASTNode e, TerminalToken tokenType) { - return createSyntaxToken(tokenManager.firstIndexAfter(e, tokenType)); - } - - /** - * @param tokenType {@link TerminalToken} - */ - private InternalSyntaxToken firstTokenIn(ASTNode e, TerminalToken tokenType) { - return createSyntaxToken(tokenManager.firstIndexIn(e, tokenType)); - } - - /** - * @param tokenTypeCandidateA {@link TerminalToken} - * @param tokenTypeCandidateB {@link TerminalToken} - * @return {@link TerminalToken} - */ - @VisibleForTesting - static int firstIndexIn(TokenManager tokenManager, ASTNode e, TerminalToken tokenTypeCandidateA, TerminalToken tokenTypeCandidateB) { - int first = tokenManager.firstIndexIn(e, ANY_TOKEN); - int last = tokenManager.lastIndexIn(e, ANY_TOKEN); - return IntStream.rangeClosed(first, last) - .filter(tokenIndex -> { - Token token = tokenManager.get(tokenIndex); - return token.tokenType == tokenTypeCandidateA || token.tokenType == tokenTypeCandidateB; - }) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Failed to find token " + tokenTypeCandidateA + " or " + tokenTypeCandidateB + - " in the tokens of a " + ASTNode.nodeClassForType(e.getNodeType()).getName())); - } - - /** - * @param tokenType {@link TerminalToken} - */ - private InternalSyntaxToken lastTokenIn(ASTNode e, TerminalToken tokenType) { - return createSyntaxToken(tokenManager.lastIndexIn(e, tokenType)); - } - - private InternalSyntaxToken createSyntaxToken(int tokenIndex) { - Token t = tokenManager.get(tokenIndex); - String value; - boolean isEOF; - if (t.tokenType == TerminalToken.TokenNameEOF) { - isEOF = true; - value = ""; - } else { - isEOF = false; - value = t.toString(tokenManager.getSource()); - } - LineColumnConverter.Pos pos = lineColumnConverter.toPos(t.originalStart); - return new InternalSyntaxToken(pos.line(), pos.columnOffset(), value, collectComments(tokenIndex), isEOF); - } - - private InternalSyntaxToken createSpecialToken(int tokenIndex) { - Token t = tokenManager.get(tokenIndex); - List comments = t.tokenType == TerminalToken.TokenNameGREATER - ? collectComments(tokenIndex) - : Collections.emptyList(); - LineColumnConverter.Pos pos = lineColumnConverter.toPos(t.originalEnd); - return new InternalSyntaxToken(pos.line(), pos.columnOffset(), ">", comments, false); - } - - private List collectComments(int tokenIndex) { - int commentIndex = tokenIndex; - while (commentIndex > 0 && isComment(tokenManager.get(commentIndex - 1))) { - commentIndex--; - } - return IntStream.range(commentIndex, tokenIndex).mapToObj(i -> { - Token t = tokenManager.get(i); - LineColumnConverter.Pos pos = lineColumnConverter.toPos(t.originalStart); - return new InternalSyntaxTrivia(convertTokenTypeToCommentKind(t), - t.toString(tokenManager.getSource()), - pos.line(), - pos.columnOffset() - ); - }) - .toList(); - } - - @VisibleForTesting - static CommentKind convertTokenTypeToCommentKind(Token token) { - return switch (token.tokenType) { - case TokenNameCOMMENT_BLOCK -> CommentKind.BLOCK; - case TokenNameCOMMENT_JAVADOC -> CommentKind.JAVADOC; - case TokenNameCOMMENT_LINE -> CommentKind.LINE; - case TokenNameCOMMENT_MARKDOWN -> CommentKind.MARKDOWN; - default -> throw new IllegalStateException("Unexpected value: " + token.tokenType); - }; - } - - /** - * {@link Token#isComment()} has an issue https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3914 - * it does not support Markdown comments. This method has to be used instead. - */ - @VisibleForTesting - static boolean isComment(Token token) { - return switch (token.tokenType) { - case TokenNameCOMMENT_BLOCK, TokenNameCOMMENT_JAVADOC, TokenNameCOMMENT_LINE, TokenNameCOMMENT_MARKDOWN -> true; - default -> false; - }; - } - - private void addEmptyStatementsToList(int tokenIndex, List list) { - while (true) { - Token token; - do { - tokenIndex++; - token = tokenManager.get(tokenIndex); - } while (isComment(token)); - - if (token.tokenType != TerminalToken.TokenNameSEMICOLON) { - break; - } - list.add(new EmptyStatementTreeImpl(createSyntaxToken(tokenIndex))); - } - } - - private JavaTree.CompilationUnitTreeImpl convertCompilationUnit(CompilationUnit e) { - PackageDeclarationTree packageDeclaration = null; - if (e.getPackage() != null) { - packageDeclaration = new JavaTree.PackageDeclarationTreeImpl( - convertAnnotations(e.getPackage().annotations()), - firstTokenIn(e.getPackage(), TerminalToken.TokenNamepackage), - convertExpression(e.getPackage().getName()), - firstTokenIn(e.getPackage(), TerminalToken.TokenNameSEMICOLON) - ); - } - - List imports = new ArrayList<>(); - for (int i = 0; i < e.imports().size(); i++) { - ImportDeclaration e2 = (ImportDeclaration) e.imports().get(i); - ExpressionTree name = convertImportName(e2.getName()); - - boolean isModuleImport = org.eclipse.jdt.core.dom.Modifier.isModule(e2.getModifiers()); - - // "on demand" means `import pkg.*;` - if (e2.isOnDemand() && !isModuleImport) { - InternalSyntaxToken dotToken = lastTokenIn(e2, TerminalToken.TokenNameDOT); - InternalSyntaxToken identifierToken = lastTokenIn(e2, TerminalToken.TokenNameMULTIPLY); - name = new MemberSelectExpressionTreeImpl(name, dotToken, new IdentifierTreeImpl(identifierToken)); - } - - InternalSyntaxToken staticKeyword = e2.isStatic() ? firstTokenIn(e2, TerminalToken.TokenNamestatic) : null; - InternalSyntaxToken moduleKeyword = isModuleImport ? firstTokenIn(e2, TerminalToken.TokenNamemodule) : null; - - JavaTree.ImportTreeImpl t = new JavaTree.ImportTreeImpl( - firstTokenIn(e2, TerminalToken.TokenNameimport), - staticKeyword, - moduleKeyword, - name, - lastTokenIn(e2, TerminalToken.TokenNameSEMICOLON) - ); - - if (!isModuleImport) { - // There is no method to resolve bindings for a module import. - t.binding = e2.resolveBinding(); - } - - imports.add(t); - - int tokenIndex = tokenManager.lastIndexIn(e2, TerminalToken.TokenNameSEMICOLON); - addEmptyStatementsToList(tokenIndex, imports); - } - - List types = new ArrayList<>(); - for (Object type : e.types()) { - processBodyDeclaration((AbstractTypeDeclaration) type, types); - } - - if (e.imports().isEmpty() && e.types().isEmpty()) { - addEmptyStatementsToList(-1, imports); - } - - return new JavaTree.CompilationUnitTreeImpl( - packageDeclaration, - imports, - types, - convertModuleDeclaration(compilationUnit.getModule()), - firstTokenAfter(e, TerminalToken.TokenNameEOF) - ); - } - - private ExpressionTree convertImportName(Name node) { - switch (node.getNodeType()) { - case ASTNode.SIMPLE_NAME: { - return new IdentifierTreeImpl(firstTokenIn(node, TerminalToken.TokenNameIdentifier)); - } - case ASTNode.QUALIFIED_NAME: { - QualifiedName e = (QualifiedName) node; - return new MemberSelectExpressionTreeImpl( - convertImportName(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - (IdentifierTreeImpl) convertImportName(e.getName())); - } - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - @Nullable - private ModuleDeclarationTree convertModuleDeclaration(@Nullable ModuleDeclaration e) { - if (e == null) { - return null; - } - List moduleDirectives = e.moduleStatements().stream() - .map(o -> convertModuleDirective((ModuleDirective) o)) - .toList(); - return new ModuleDeclarationTreeImpl( - convertAnnotations(e.annotations()), - e.isOpen() ? firstTokenIn(e, TerminalToken.TokenNameopen) : null, - firstTokenBefore(e.getName(), TerminalToken.TokenNamemodule), - convertModuleName(e.getName()), - firstTokenAfter(e.getName(), TerminalToken.TokenNameLBRACE), - moduleDirectives, - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private ModuleNameTreeImpl convertModuleName(Name node) { - switch (node.getNodeType()) { - case ASTNode.QUALIFIED_NAME: { - QualifiedName e = (QualifiedName) node; - ModuleNameTreeImpl t = convertModuleName(e.getQualifier()); - t.add(new IdentifierTreeImpl(firstTokenIn(e.getName(), TerminalToken.TokenNameIdentifier))); - return t; - } - case ASTNode.SIMPLE_NAME: { - ModuleNameTreeImpl t = ModuleNameTreeImpl.emptyList(); - t.add(new IdentifierTreeImpl(firstTokenIn(node, TerminalToken.TokenNameIdentifier))); - return t; - } - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - private ModuleNameListTreeImpl convertModuleNames(List list) { - ModuleNameListTreeImpl t = ModuleNameListTreeImpl.emptyList(); - for (int i = 0; i < list.size(); i++) { - Name o = (Name) list.get(i); - if (i > 0) { - t.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - t.add(convertModuleName(o)); - } - return t; - } - - private ModuleDirectiveTree convertModuleDirective(ModuleDirective node) { - switch (node.getNodeType()) { - case ASTNode.REQUIRES_DIRECTIVE: { - RequiresDirective e = (RequiresDirective) node; - List modifiers = new ArrayList<>(); - for (Object o : e.modifiers()) { - switch (((ModuleModifier) o).getKeyword().toString()) { - case "static": - modifiers.add(new ModifierKeywordTreeImpl(Modifier.STATIC, firstTokenIn((ASTNode) o, ANY_TOKEN))); - break; - case "transitive": - modifiers.add(new ModifierKeywordTreeImpl(Modifier.TRANSITIVE, firstTokenIn((ASTNode) o, ANY_TOKEN))); - break; - default: - throw new IllegalStateException(); - } - } - return new RequiresDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamerequires), - new ModifiersTreeImpl(modifiers), - convertModuleName(e.getName()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - case ASTNode.EXPORTS_DIRECTIVE: { - ExportsDirective e = (ExportsDirective) node; - return new ExportsDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameexports), - convertExpression(e.getName()), - e.modules().isEmpty() ? null : firstTokenAfter(e.getName(), TerminalToken.TokenNameto), - convertModuleNames(e.modules()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - case ASTNode.OPENS_DIRECTIVE: { - OpensDirective e = (OpensDirective) node; - return new OpensDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameopens), - convertExpression(e.getName()), - e.modules().isEmpty() ? null : firstTokenAfter(e.getName(), TerminalToken.TokenNameto), - convertModuleNames(e.modules()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - case ASTNode.USES_DIRECTIVE: { - UsesDirective e = (UsesDirective) node; - return new UsesDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameuses), - (TypeTree) convertExpression(e.getName()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - case ASTNode.PROVIDES_DIRECTIVE: { - ProvidesDirective e = (ProvidesDirective) node; - QualifiedIdentifierListTreeImpl typeNames = QualifiedIdentifierListTreeImpl.emptyList(); - for (int i = 0; i < e.implementations().size(); i++) { - Name o = (Name) e.implementations().get(i); - if (i > 0) { - typeNames.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - typeNames.add((TypeTree) convertExpression(o)); - } - return new ProvidesDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameprovides), - (TypeTree) convertExpression(e.getName()), - firstTokenAfter(e.getName(), TerminalToken.TokenNamewith), - typeNames, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - /** Converts implicitly declared unnamed class at the top level of a compact compilation unit. */ - private ClassTreeImpl convertUnnamedClassDeclaration(AbstractTypeDeclaration e) { - List members = new ArrayList<>(); - - for (Object o : e.bodyDeclarations()) { - processBodyDeclaration((BodyDeclaration) o, members); - } - - ClassTreeImpl t = new ClassTreeImpl(Tree.Kind.IMPLICIT_CLASS, null, members, null); - - t.typeBinding = e.resolveBinding(); - declaration(t.typeBinding, t); - - return t; - } - - private ClassTreeImpl convertTypeDeclaration(AbstractTypeDeclaration e) { - if (e.getNodeType() == ASTNode.UNNAMED_CLASS) { - return convertUnnamedClassDeclaration(e); - } - - List members = new ArrayList<>(); - - int leftBraceTokenIndex = findLeftBraceTokenIndex(e); - addEmptyStatementsToList(leftBraceTokenIndex, members); - - for (Object o : e.bodyDeclarations()) { - processBodyDeclaration((BodyDeclaration) o, members); - } - - ModifiersTreeImpl modifiers = convertModifiers(e.modifiers()); - IdentifierTreeImpl name = createSimpleName(e.getName()); - - InternalSyntaxToken openBraceToken = createSyntaxToken(leftBraceTokenIndex); - InternalSyntaxToken closeBraceToken = lastTokenIn(e, TerminalToken.TokenNameRBRACE); - - final ClassTreeImpl t; - switch (e.getNodeType()) { - case ASTNode.TYPE_DECLARATION: - t = convertTypeDeclaration((TypeDeclaration) e, modifiers, name, openBraceToken, members, closeBraceToken); - break; - case ASTNode.ENUM_DECLARATION: - t = convertEnumDeclaration((EnumDeclaration) e, modifiers, name, openBraceToken, members, closeBraceToken); - break; - case ASTNode.RECORD_DECLARATION: - t = convertRecordDeclaration((RecordDeclaration) e, modifiers, name, openBraceToken, members, closeBraceToken); - break; - case ASTNode.ANNOTATION_TYPE_DECLARATION: - t = convertAnnotationTypeDeclaration((AnnotationTypeDeclaration) e, modifiers, name, openBraceToken, members, closeBraceToken); - break; - default: - throw new IllegalStateException(ASTNode.nodeClassForType(e.getNodeType()).toString()); - } - - // no-op for annotation-types - completeSuperInterfaces(e, t); - - t.typeBinding = e.resolveBinding(); - declaration(t.typeBinding, t); - - return t; - } - - private ClassTreeImpl convertTypeDeclaration(TypeDeclaration e, ModifiersTreeImpl modifiers, IdentifierTreeImpl name, - InternalSyntaxToken openBraceToken, List members, InternalSyntaxToken closeBraceToken) { - InternalSyntaxToken declarationKeyword = firstTokenBefore(e.getName(), e.isInterface() ? TerminalToken.TokenNameinterface : TerminalToken.TokenNameclass); - ClassTreeImpl t = new ClassTreeImpl(e.isInterface() ? Tree.Kind.INTERFACE : Tree.Kind.CLASS, openBraceToken, members, closeBraceToken) - .complete(modifiers, declarationKeyword, name) - .completeTypeParameters(convertTypeParameters(e.typeParameters())); - - if (!e.permittedTypes().isEmpty()) { - List permittedTypes = e.permittedTypes(); - InternalSyntaxToken permitsKeyword = firstTokenBefore((Type) permittedTypes.get(0), TerminalToken.TokenNameRestrictedIdentifierpermits); - QualifiedIdentifierListTreeImpl classPermittedTypes = QualifiedIdentifierListTreeImpl.emptyList(); - - convertSeparatedTypeList(permittedTypes, classPermittedTypes); - t.completePermittedTypes(permitsKeyword, classPermittedTypes); - } - - if (!e.isInterface() && e.getSuperclassType() != null) { - Type superclassType = e.getSuperclassType(); - t.completeSuperclass(firstTokenBefore(superclassType, TerminalToken.TokenNameextends), convertType(superclassType)); - } - return t; - } - - private ClassTreeImpl convertEnumDeclaration(EnumDeclaration e, ModifiersTreeImpl modifiers, IdentifierTreeImpl name, - InternalSyntaxToken openBraceToken, List members, InternalSyntaxToken closeBraceToken) { - List enumConstants = ((List) e.enumConstants()).stream() - .map(EnumConstantDeclaration.class::cast) - .map(this::processEnumConstantDeclaration) - .toList(); - members.addAll(0, enumConstants); - - InternalSyntaxToken declarationKeyword = firstTokenBefore(e.getName(), TerminalToken.TokenNameenum); - return new ClassTreeImpl(Tree.Kind.ENUM, openBraceToken, members, closeBraceToken) - .complete(modifiers, declarationKeyword, name); - } - - private ClassTreeImpl convertAnnotationTypeDeclaration(AnnotationTypeDeclaration e, ModifiersTreeImpl modifiers, IdentifierTreeImpl name, - InternalSyntaxToken openBraceToken, List members, InternalSyntaxToken closeBraceToken) { - InternalSyntaxToken declarationKeyword = firstTokenBefore(e.getName(), TerminalToken.TokenNameinterface); - return new ClassTreeImpl(Tree.Kind.ANNOTATION_TYPE, openBraceToken, members, closeBraceToken) - .complete(modifiers, declarationKeyword, name) - .completeAtToken(firstTokenBefore(e.getName(), TerminalToken.TokenNameAT)); - } - - private ClassTreeImpl convertRecordDeclaration(RecordDeclaration e, ModifiersTreeImpl modifiers, IdentifierTreeImpl name, - InternalSyntaxToken openBraceToken, List members, InternalSyntaxToken closeBraceToken) { - InternalSyntaxToken declarationKeyword = firstTokenBefore(e.getName(), TerminalToken.TokenNameRestrictedIdentifierrecord); - List recordComponents = e.recordComponents(); - InternalSyntaxToken openParen = firstTokenAfter(e.getName(), TerminalToken.TokenNameLPAREN); - InternalSyntaxToken closeParen = firstTokenAfter( - recordComponents.isEmpty() ? e.getName() : (ASTNode) recordComponents.get(recordComponents.size() - 1), - TerminalToken.TokenNameRPAREN); - return new ClassTreeImpl(Tree.Kind.RECORD, openBraceToken, members, closeBraceToken) - .complete(modifiers, declarationKeyword, name) - .completeTypeParameters(convertTypeParameters(e.typeParameters())) - .completeRecordComponents(openParen, convertRecordComponents(e), closeParen); - } - - private List convertRecordComponents(RecordDeclaration e) { - List recordComponents = new ArrayList<>(); - - for (int i = 0; i < e.recordComponents().size(); i++) { - SingleVariableDeclaration o = (SingleVariableDeclaration) e.recordComponents().get(i); - VariableTreeImpl recordComponent = convertVariable(o); - if (i < e.recordComponents().size() - 1) { - recordComponent.setEndToken(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - recordComponents.add(recordComponent); - } - return recordComponents; - } - - private int findLeftBraceTokenIndex(AbstractTypeDeclaration e) { - // TODO try to simplify, note that type annotations can contain LBRACE - if (e.getNodeType() == ASTNode.ENUM_DECLARATION) { - EnumDeclaration enumDeclaration = (EnumDeclaration) e; - if (!enumDeclaration.enumConstants().isEmpty()) { - return tokenManager.firstIndexBefore((ASTNode) enumDeclaration.enumConstants().get(0), TerminalToken.TokenNameLBRACE); - } - if (!enumDeclaration.bodyDeclarations().isEmpty()) { - return tokenManager.firstIndexBefore((ASTNode) e.bodyDeclarations().get(0), TerminalToken.TokenNameLBRACE); - } - return tokenManager.lastIndexIn(e, TerminalToken.TokenNameLBRACE); - } - if (!e.bodyDeclarations().isEmpty()) { - // for records, bodyDeclarations may not be in the order encountered in file, for classes they are - List bodyDeclarations = e.bodyDeclarations(); - BodyDeclaration firstDeclaration = bodyDeclarations.get(0); - for (int i = 1; i < bodyDeclarations.size(); i++) { - BodyDeclaration declaration = bodyDeclarations.get(i); - if (firstDeclaration.getStartPosition() > declaration.getStartPosition()) { - firstDeclaration = declaration; - } - } - return tokenManager.firstIndexBefore(firstDeclaration, TerminalToken.TokenNameLBRACE); - } - return tokenManager.lastIndexIn(e, TerminalToken.TokenNameLBRACE); - } - - private void completeSuperInterfaces(AbstractTypeDeclaration e, ClassTreeImpl t) { - List superInterfaces = superInterfaceTypes(e); - if (!superInterfaces.isEmpty()) { - QualifiedIdentifierListTreeImpl interfaces = QualifiedIdentifierListTreeImpl.emptyList(); - convertSeparatedTypeList(superInterfaces, interfaces); - - ASTNode firstInterface = (ASTNode) superInterfaces.get(0); - InternalSyntaxToken keyword = firstTokenBefore(firstInterface, t.is(Tree.Kind.INTERFACE) ? TerminalToken.TokenNameextends : TerminalToken.TokenNameimplements); - t.completeInterfaces(keyword, interfaces); - } - } - - private static List superInterfaceTypes(AbstractTypeDeclaration e) { - switch (e.getNodeType()) { - case ASTNode.TYPE_DECLARATION: - return ((TypeDeclaration) e).superInterfaceTypes(); - case ASTNode.ENUM_DECLARATION: - return ((EnumDeclaration) e).superInterfaceTypes(); - case ASTNode.RECORD_DECLARATION: - return ((RecordDeclaration) e).superInterfaceTypes(); - case ASTNode.ANNOTATION_TYPE_DECLARATION, ASTNode.UNNAMED_CLASS: - return Collections.emptyList(); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(e.getNodeType()).toString()); - } - } - - private void convertSeparatedTypeList(List source, ListTree target) { - for (int i = 0; i < source.size(); i++) { - Type o = source.get(i); - T tree = (T) convertType(o); - if (i > 0) { - target.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - target.add(tree); - } - } - - private EnumConstantTreeImpl processEnumConstantDeclaration(EnumConstantDeclaration e) { - final int openParTokenIndex = firstTokenIndexAfter(e.getName()); - final InternalSyntaxToken openParToken; - final InternalSyntaxToken closeParToken; - if (tokenManager.get(openParTokenIndex).tokenType == TerminalToken.TokenNameLPAREN) { - openParToken = createSyntaxToken(openParTokenIndex); - closeParToken = firstTokenAfter( - e.arguments().isEmpty() - ? e.getName() - : (ASTNode) e.arguments().get(e.arguments().size() - 1), - TerminalToken.TokenNameRPAREN); - } else { - openParToken = null; - closeParToken = null; - } - - final ArgumentListTreeImpl arguments = convertArguments(openParToken, e.arguments(), closeParToken); - ClassTreeImpl classBody = null; - if (e.getAnonymousClassDeclaration() != null) { - List members = new ArrayList<>(); - for (Object o : e.getAnonymousClassDeclaration().bodyDeclarations()) { - processBodyDeclaration((BodyDeclaration) o, members); - } - classBody = new ClassTreeImpl( - Tree.Kind.CLASS, - firstTokenIn(e.getAnonymousClassDeclaration(), TerminalToken.TokenNameLBRACE), - members, - lastTokenIn(e.getAnonymousClassDeclaration(), TerminalToken.TokenNameRBRACE) - ); - classBody.typeBinding = e.getAnonymousClassDeclaration().resolveBinding(); - declaration(classBody.typeBinding, classBody); - } - - final int separatorTokenIndex = firstTokenIndexAfter(e); - final InternalSyntaxToken separatorToken; - switch (tokenManager.get(separatorTokenIndex).tokenType) { - case TokenNameCOMMA, - TokenNameSEMICOLON: - separatorToken = createSyntaxToken(separatorTokenIndex); - break; - case TokenNameRBRACE: - separatorToken = null; - break; - default: - throw new IllegalStateException(); - } - - IdentifierTreeImpl identifier = createSimpleName(e.getName()); - if (e.getAnonymousClassDeclaration() == null) { - identifier.binding = excludeRecovery(e.resolveConstructorBinding(), arguments.size()); - } else { - identifier.binding = findConstructorForAnonymousClass(e.getAST(), identifier.typeBinding, e.resolveConstructorBinding()); - } - usage(identifier.binding, identifier); - - EnumConstantTreeImpl t = new EnumConstantTreeImpl( - convertModifiers(e.modifiers()), - identifier, - new NewClassTreeImpl(identifier, arguments, classBody), - separatorToken - ); - t.variableBinding = e.resolveVariable(); - declaration(t.variableBinding, t); - return t; - } - - private void processBodyDeclaration(BodyDeclaration node, List members) { - final int lastTokenIndex; - - switch (node.getNodeType()) { - case ASTNode.ANNOTATION_TYPE_DECLARATION, - ASTNode.ENUM_DECLARATION, - ASTNode.RECORD_DECLARATION, - ASTNode.TYPE_DECLARATION, - ASTNode.UNNAMED_CLASS: - lastTokenIndex = processTypeDeclaration((AbstractTypeDeclaration) node, members); - break; - case ASTNode.ANNOTATION_TYPE_MEMBER_DECLARATION: - lastTokenIndex = processAnnotationTypeMemberDeclaration((AnnotationTypeMemberDeclaration) node, members); - break; - case ASTNode.INITIALIZER: - lastTokenIndex = processInitializerDeclaration((Initializer) node, members); - break; - case ASTNode.METHOD_DECLARATION: - lastTokenIndex = processMethodDeclaration((MethodDeclaration) node, members); - break; - case ASTNode.FIELD_DECLARATION: - lastTokenIndex = processFieldDeclaration((FieldDeclaration) node, members); - break; - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - - addEmptyStatementsToList(lastTokenIndex, members); - } - - private int processTypeDeclaration(AbstractTypeDeclaration node, List members) { - members.add(convertTypeDeclaration(node)); - return tokenManager.lastIndexIn(node, TerminalToken.TokenNameRBRACE); - } - - private int processAnnotationTypeMemberDeclaration(AnnotationTypeMemberDeclaration e, List members) { - FormalParametersListTreeImpl parameters = new FormalParametersListTreeImpl( - firstTokenAfter(e.getName(), TerminalToken.TokenNameLPAREN), - firstTokenAfter(e.getName(), TerminalToken.TokenNameRPAREN)); - - Expression defaultExpression = e.getDefault(); - InternalSyntaxToken defaultToken = defaultExpression == null ? null : firstTokenBefore(defaultExpression, TerminalToken.TokenNamedefault); - ExpressionTree defaultValue = defaultExpression == null ? null : convertExpression(defaultExpression); - - MethodTreeImpl t = new MethodTreeImpl(parameters, defaultToken, defaultValue) - .complete(convertType(e.getType()), createSimpleName(e.getName()), lastTokenIn(e, TerminalToken.TokenNameSEMICOLON)) - .completeWithModifiers(convertModifiers(e.modifiers())); - - t.methodBinding = e.resolveBinding(); - declaration(t.methodBinding, t); - - members.add(t); - return tokenManager.lastIndexIn(e, TerminalToken.TokenNameSEMICOLON); - } - - private int processInitializerDeclaration(Initializer e, List members) { - BlockTreeImpl blockTree = convertBlock(e.getBody()); - if (org.eclipse.jdt.core.dom.Modifier.isStatic(e.getModifiers())) { - members.add(new StaticInitializerTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamestatic), - (InternalSyntaxToken) blockTree.openBraceToken(), - blockTree.body(), - (InternalSyntaxToken) blockTree.closeBraceToken())); - } else { - members.add(new BlockTreeImpl( - Tree.Kind.INITIALIZER, - (InternalSyntaxToken) blockTree.openBraceToken(), - blockTree.body(), - (InternalSyntaxToken) blockTree.closeBraceToken())); - } - return tokenManager.lastIndexIn(e, TerminalToken.TokenNameRBRACE); - } - - private int processMethodDeclaration(MethodDeclaration e, List members) { - List p = e.parameters(); - final FormalParametersListTreeImpl formalParameters; - if (e.isCompactConstructor()) { - // only used for records - formalParameters = new FormalParametersListTreeImpl(null, null); - } else { - InternalSyntaxToken openParen = firstTokenAfter(e.getName(), TerminalToken.TokenNameLPAREN); - InternalSyntaxToken closeParen = firstTokenAfter(p.isEmpty() ? e.getName() : (ASTNode) p.get(p.size() - 1), TerminalToken.TokenNameRPAREN); - formalParameters = new FormalParametersListTreeImpl(openParen, closeParen); - } - - for (int i = 0; i < p.size(); i++) { - SingleVariableDeclaration o = (SingleVariableDeclaration) p.get(i); - VariableTreeImpl parameter = convertVariable(o); - if (i < p.size() - 1) { - parameter.setEndToken(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - formalParameters.add(parameter); - } - - QualifiedIdentifierListTreeImpl thrownExceptionTypes = QualifiedIdentifierListTreeImpl.emptyList(); - List tt = e.thrownExceptionTypes(); - convertSeparatedTypeList(tt, thrownExceptionTypes); - - Block body = e.getBody(); - Type returnType = e.getReturnType2(); - InternalSyntaxToken throwsToken = tt.isEmpty() ? null : firstTokenBefore((Type) tt.get(0), TerminalToken.TokenNamethrows); - InternalSyntaxToken semcolonToken = body == null ? lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) : null; - MethodTreeImpl t = new MethodTreeImpl( - returnType == null ? null : applyExtraDimensions(convertType(returnType), e.extraDimensions()), - createSimpleName(e.getName()), - formalParameters, - throwsToken, - thrownExceptionTypes, - body == null ? null : convertBlock(body), - semcolonToken - ).completeWithModifiers( - convertModifiers(e.modifiers()) - ).completeWithTypeParameters( - convertTypeParameters(e.typeParameters()) - ); - t.methodBinding = e.resolveBinding(); - declaration(t.methodBinding, t); - - members.add(t); - return tokenManager.lastIndexIn(e, body == null ? TerminalToken.TokenNameSEMICOLON : TerminalToken.TokenNameRBRACE); - } - - private int processFieldDeclaration(FieldDeclaration fieldDeclaration, List members) { - ModifiersTreeImpl modifiers = convertModifiers(fieldDeclaration.modifiers()); - TypeTree type = convertType(fieldDeclaration.getType()); - - for (int i = 0; i < fieldDeclaration.fragments().size(); i++) { - VariableDeclarationFragment fragment = (VariableDeclarationFragment) fieldDeclaration.fragments().get(i); - VariableTreeImpl t = new VariableTreeImpl(createSimpleName(fragment.getName())) - .completeModifiersAndType(modifiers, applyExtraDimensions(type, fragment.extraDimensions())); - - if (fragment.getInitializer() != null) { - t.completeTypeAndInitializer(t.type(), firstTokenAfter(fragment.getName(), TerminalToken.TokenNameEQUAL), convertExpression(fragment.getInitializer())); - } - - t.setEndToken(firstTokenAfter(fragment, i + 1 < fieldDeclaration.fragments().size() ? TerminalToken.TokenNameCOMMA : TerminalToken.TokenNameSEMICOLON)); - t.variableBinding = fragment.resolveBinding(); - declaration(t.variableBinding, t); - - members.add(t); - } - return tokenManager.lastIndexIn(fieldDeclaration, TerminalToken.TokenNameSEMICOLON); - } - - private ArgumentListTreeImpl convertArguments(@Nullable InternalSyntaxToken openParen, List list, @Nullable InternalSyntaxToken closeParen) { - ArgumentListTreeImpl arguments = ArgumentListTreeImpl.emptyList().complete(openParen, closeParen); - for (int i = 0; i < list.size(); i++) { - Expression o = (Expression) list.get(i); - arguments.add(convertExpression(o)); - if (i < list.size() - 1) { - arguments.separators().add(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - } - return arguments; - } - - @Nullable - private TypeArgumentListTreeImpl convertTypeArguments(List list) { - if (list.isEmpty()) { - return null; - } - ASTNode last = (ASTNode) list.get(list.size() - 1); - int tokenIndex = tokenManager.firstIndexAfter(last, ANY_TOKEN); - while (isComment(tokenManager.get(tokenIndex))) { - tokenIndex++; - } - return convertTypeArguments( - firstTokenBefore((ASTNode) list.get(0), TerminalToken.TokenNameLESS), - list, - // TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT vs TerminalToken.TokenNameGREATER - createSpecialToken(tokenIndex) - ); - } - - private TypeArgumentListTreeImpl convertTypeArguments(InternalSyntaxToken l, List list, InternalSyntaxToken g) { - TypeArgumentListTreeImpl typeArguments = new TypeArgumentListTreeImpl(l, g); - for (int i = 0; i < list.size(); i++) { - Type o = (Type) list.get(i); - if (i > 0) { - typeArguments.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - typeArguments.add(convertType(o)); - } - return typeArguments; - } - - private TypeParameterListTreeImpl convertTypeParameters(List list) { - if (list.isEmpty()) { - return new TypeParameterListTreeImpl(); - } - ASTNode last = (ASTNode) list.get(list.size() - 1); - int tokenIndex = tokenManager.firstIndexAfter(last, ANY_TOKEN); - while (isComment(tokenManager.get(tokenIndex))) { - tokenIndex++; - } - TypeParameterListTreeImpl t = new TypeParameterListTreeImpl( - firstTokenBefore((ASTNode) list.get(0), TerminalToken.TokenNameLESS), - // TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT vs TerminalToken.TokenNameGREATER - createSpecialToken(tokenIndex) - ); - for (int i = 0; i < list.size(); i++) { - TypeParameter o = (TypeParameter) list.get(i); - if (i > 0) { - t.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - t.add(convertTypeParameter(o)); - } - return t; - } - - private TypeParameterTree convertTypeParameter(TypeParameter e) { - IdentifierTreeImpl i = createSimpleName(e.getName()); - // TODO why ECJ uses IExtendedModifier here instead of Annotation ? - i.complete(convertAnnotations(e.modifiers())); - TypeParameterTreeImpl t; - List typeBounds = e.typeBounds(); - if (typeBounds.isEmpty()) { - t = new TypeParameterTreeImpl(i); - } else { - QualifiedIdentifierListTreeImpl bounds = QualifiedIdentifierListTreeImpl.emptyList(); - for (int j = 0; j < typeBounds.size(); j++) { - Object o = typeBounds.get(j); - bounds.add(convertType((Type) o)); - if (j < typeBounds.size() - 1) { - bounds.separators().add(firstTokenAfter((ASTNode) o, TerminalToken.TokenNameAND)); - } - } - t = new TypeParameterTreeImpl( - i, - firstTokenAfter(e.getName(), TerminalToken.TokenNameextends), - bounds - ); - } - t.typeBinding = e.resolveBinding(); - return t; - } - - /** - * @param extraDimensions list of {@link org.eclipse.jdt.core.dom.Dimension} - */ - private TypeTree applyExtraDimensions(TypeTree type, List extraDimensions) { - ITypeBinding typeBinding = ((AbstractTypedTree) type).typeBinding; - for (int i = 0; i < extraDimensions.size(); i++) { - Dimension e = (Dimension) extraDimensions.get(i); - type = new JavaTree.ArrayTypeTreeImpl( - type, - (List) convertAnnotations(e.annotations()), - firstTokenIn(e, TerminalToken.TokenNameLBRACKET), - firstTokenIn(e, TerminalToken.TokenNameRBRACKET) - ); - if (typeBinding != null) { - ((JavaTree.ArrayTypeTreeImpl) type).typeBinding = typeBinding.createArrayType(i + 1); - } - } - return type; - } - - private VariableTreeImpl convertVariable(TypePattern typePattern) { - if (typePattern.getAST().apiLevel() < AST.JLS22) { - return convertVariable(typePattern.getPatternVariable()); - } - VariableDeclaration variableDeclaration = typePattern.getPatternVariable2(); - if (variableDeclaration instanceof VariableDeclarationFragment declarationFragment) { - return convertVariable(declarationFragment); - } else { - return convertVariable((SingleVariableDeclaration) variableDeclaration); - } - } - - private VariableTreeImpl convertVariable(VariableDeclarationFragment declarationFragment) { - return completeInitializerAndBinding( - new VariableTreeImpl(createSimpleName(declarationFragment.getName())), - declarationFragment); - } - - private VariableTreeImpl convertVariable(SingleVariableDeclaration e) { - // TODO are extraDimensions and varargs mutually exclusive? - TypeTree type = convertType(e.getType()); - type = applyExtraDimensions(type, e.extraDimensions()); - if (e.isVarargs()) { - ITypeBinding typeBinding = ((AbstractTypedTree) type).typeBinding; - type = new JavaTree.ArrayTypeTreeImpl( - type, - (List) convertAnnotations(e.varargsAnnotations()), - firstTokenAfter(e.getType(), TerminalToken.TokenNameELLIPSIS) - ); - if (typeBinding != null) { - ((JavaTree.ArrayTypeTreeImpl) type).typeBinding = typeBinding.createArrayType(1); - } - } - - VariableTreeImpl t = new VariableTreeImpl( - convertModifiers(e.modifiers()), - type, - createSimpleName(e.getName()) - ); - return completeInitializerAndBinding(t, e); - } - - private VariableTreeImpl completeInitializerAndBinding(VariableTreeImpl t, VariableDeclaration e) { - if (e.getInitializer() != null) { - t.completeTypeAndInitializer( - t.type(), - firstTokenAfter(e.getName(), TerminalToken.TokenNameEQUAL), - convertExpression(e.getInitializer()) - ); - } - t.variableBinding = e.resolveBinding(); - if (t.variableBinding != null) { - if (t.type() instanceof InferedTypeTree inferredType) { - inferredType.typeBinding = t.variableBinding.getType(); - } - declaration(t.variableBinding, t); - } - return t; - } - - private void addVariableToList(VariableDeclarationExpression e2, List list) { - ModifiersTreeImpl modifiers = convertModifiers(e2.modifiers()); - TypeTree type = convertType(e2.getType()); - - for (int i = 0; i < e2.fragments().size(); i++) { - VariableDeclarationFragment fragment = (VariableDeclarationFragment) e2.fragments().get(i); - VariableTreeImpl t = new VariableTreeImpl(createSimpleName(fragment.getName())); - t.completeModifiers(modifiers); - if (fragment.getInitializer() == null) { - t.completeType(applyExtraDimensions(type, fragment.extraDimensions())); - } else { - t.completeTypeAndInitializer( - applyExtraDimensions(type, fragment.extraDimensions()), - firstTokenBefore(fragment.getInitializer(), TerminalToken.TokenNameEQUAL), - convertExpression(fragment.getInitializer()) - ); - } - if (i < e2.fragments().size() - 1) { - t.setEndToken(firstTokenAfter(fragment, TerminalToken.TokenNameCOMMA)); - } - t.variableBinding = fragment.resolveBinding(); - declaration(t.variableBinding, t); - list.add(t); - } - } - - private VarTypeTreeImpl convertVarType(SimpleType simpleType) { - VarTypeTreeImpl varTree = new VarTypeTreeImpl(firstTokenIn(simpleType.getName(), TerminalToken.TokenNameIdentifier)); - varTree.typeBinding = simpleType.resolveBinding(); - return varTree; - } - - private IdentifierTreeImpl createSimpleName(SimpleName e) { - int tokenIndex = firstIndexIn(tokenManager, e, TerminalToken.TokenNameIdentifier, TerminalToken.TokenNameUNDERSCORE); - Token token = tokenManager.get(tokenIndex); - boolean isUnnamedVariable = token.tokenType == TerminalToken.TokenNameUNDERSCORE; - IdentifierTreeImpl t = new IdentifierTreeImpl(createSyntaxToken(tokenIndex), isUnnamedVariable); - t.typeBinding = e.resolveTypeBinding(); - t.binding = e.resolveBinding(); - return t; - } - - private BlockTreeImpl convertBlock(Block e) { - List statements = new ArrayList<>(); - for (Object o : e.statements()) { - addStatementToList((Statement) o, statements); - } - return new BlockTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameLBRACE), - statements, - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private void addStatementToList(Statement node, List statements) { - if (node.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT) { - VariableDeclarationStatement e = (VariableDeclarationStatement) node; - TypeTree tType = convertType(e.getType()); - ModifiersTreeImpl modifiers = convertModifiers(e.modifiers()); - for (int i = 0; i < e.fragments().size(); i++) { - VariableDeclarationFragment fragment = (VariableDeclarationFragment) e.fragments().get(i); - VariableTreeImpl t = new VariableTreeImpl(createSimpleName(fragment.getName())) - .completeType(applyExtraDimensions(tType, fragment.extraDimensions())) - .completeModifiers(modifiers); - Expression initalizer = fragment.getInitializer(); - if (initalizer != null) { - InternalSyntaxToken equalToken = firstTokenAfter(fragment.getName(), TerminalToken.TokenNameEQUAL); - t.completeTypeAndInitializer(t.type(), equalToken, convertExpression(initalizer)); - } - TerminalToken endTokenType = i < e.fragments().size() - 1 ? TerminalToken.TokenNameCOMMA : TerminalToken.TokenNameSEMICOLON; - t.setEndToken(firstTokenAfter(fragment, endTokenType)); - - t.variableBinding = fragment.resolveBinding(); - declaration(t.variableBinding, t); - statements.add(t); - } - } else { - statements.add(createStatement(node)); - } - } - - private StatementTree createStatement(Statement node) { - switch (node.getNodeType()) { - case ASTNode.BLOCK: - return convertBlock((Block) node); - case ASTNode.EMPTY_STATEMENT: - return convertEmptyStatement((EmptyStatement) node); - case ASTNode.RETURN_STATEMENT: - return convertReturn((ReturnStatement) node); - case ASTNode.FOR_STATEMENT: - return convertFor((ForStatement) node); - case ASTNode.WHILE_STATEMENT: - return convertWhile((WhileStatement) node); - case ASTNode.IF_STATEMENT: - return convertIf((IfStatement) node); - case ASTNode.BREAK_STATEMENT: - return convertBreak((BreakStatement) node); - case ASTNode.DO_STATEMENT: - return convertDoWhile((DoStatement) node); - case ASTNode.ASSERT_STATEMENT: - return convertAssert((AssertStatement) node); - case ASTNode.SWITCH_STATEMENT: - return convertSwitchStatement((SwitchStatement) node); - case ASTNode.SYNCHRONIZED_STATEMENT: - return convertSynchronized((SynchronizedStatement) node); - case ASTNode.EXPRESSION_STATEMENT: - return convertExpressionStatement((ExpressionStatement) node); - case ASTNode.CONTINUE_STATEMENT: - return convertContinue((ContinueStatement) node); - case ASTNode.LABELED_STATEMENT: - return convertLabel((LabeledStatement) node); - case ASTNode.ENHANCED_FOR_STATEMENT: - return convertForeach((EnhancedForStatement) node); - case ASTNode.THROW_STATEMENT: - return convertThrow((ThrowStatement) node); - case ASTNode.TRY_STATEMENT: - return convertTry((TryStatement) node); - case ASTNode.TYPE_DECLARATION_STATEMENT: - return convertTypeDeclaration(((TypeDeclarationStatement) node).getDeclaration()); - case ASTNode.CONSTRUCTOR_INVOCATION: - return convertConstructorInvocation((ConstructorInvocation) node); - case ASTNode.SUPER_CONSTRUCTOR_INVOCATION: - return convertSuperConstructorInvocation((SuperConstructorInvocation) node); - case ASTNode.YIELD_STATEMENT: - return convertYield((YieldStatement) node); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - private EmptyStatementTreeImpl convertEmptyStatement(EmptyStatement e) { - return new EmptyStatementTreeImpl(lastTokenIn(e, TerminalToken.TokenNameSEMICOLON)); - } - - private ReturnStatementTreeImpl convertReturn(ReturnStatement e) { - Expression expression = e.getExpression(); - return new ReturnStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamereturn), - expression == null ? null : convertExpression(expression), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private ForStatementTreeImpl convertFor(ForStatement e) { - StatementListTreeImpl forInitStatement = StatementListTreeImpl.emptyList(); - for (int i = 0; i < e.initializers().size(); i++) { - Expression o = (Expression) e.initializers().get(i); - if (i > 0) { - forInitStatement.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - if (ASTNode.VARIABLE_DECLARATION_EXPRESSION == o.getNodeType()) { - addVariableToList((VariableDeclarationExpression) o, forInitStatement); - } else { - forInitStatement.add(new ExpressionStatementTreeImpl(convertExpression(o), null)); - } - } - - StatementListTreeImpl forUpdateStatement = StatementListTreeImpl.emptyList(); - for (int i = 0; i < e.updaters().size(); i++) { - Expression o = (Expression) e.updaters().get(i); - if (i > 0) { - forUpdateStatement.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - forUpdateStatement.add(new ExpressionStatementTreeImpl(convertExpression(o), null)); - } - - final int firstSemicolonTokenIndex = e.initializers().isEmpty() - ? tokenManager.firstIndexIn(e, TerminalToken.TokenNameSEMICOLON) - : tokenManager.firstIndexAfter((ASTNode) e.initializers().get(e.initializers().size() - 1), TerminalToken.TokenNameSEMICOLON); - Expression expression = e.getExpression(); - final int secondSemicolonTokenIndex = expression == null - ? nextTokenIndex(firstSemicolonTokenIndex, TerminalToken.TokenNameSEMICOLON) - : tokenManager.firstIndexAfter(expression, TerminalToken.TokenNameSEMICOLON); - - return new ForStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamefor), - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - forInitStatement, - createSyntaxToken(firstSemicolonTokenIndex), - expression == null ? null : convertExpression(expression), - createSyntaxToken(secondSemicolonTokenIndex), - forUpdateStatement, - firstTokenBefore(e.getBody(), TerminalToken.TokenNameRPAREN), - createStatement(e.getBody()) - ); - } - - private WhileStatementTreeImpl convertWhile(WhileStatement e) { - Expression expression = e.getExpression(); - return new WhileStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamewhile), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - createStatement(e.getBody()) - ); - } - - private IfStatementTreeImpl convertIf(IfStatement e) { - Expression expression = e.getExpression(); - Statement thenStatement = e.getThenStatement(); - Statement elseStatement = e.getElseStatement(); - return new IfStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameif), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - createStatement(thenStatement), - elseStatement == null ? null : firstTokenAfter(thenStatement, TerminalToken.TokenNameelse), - elseStatement == null ? null : createStatement(elseStatement) - ); - } - - private BreakStatementTreeImpl convertBreak(BreakStatement e) { - IdentifierTreeImpl identifier = e.getLabel() == null ? null : createSimpleName(e.getLabel()); - usageLabel(identifier); - return new BreakStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamebreak), - identifier, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private DoWhileStatementTreeImpl convertDoWhile(DoStatement e) { - Statement body = e.getBody(); - Expression expression = e.getExpression(); - return new DoWhileStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamedo), - createStatement(body), - firstTokenAfter(body, TerminalToken.TokenNamewhile), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private AssertStatementTreeImpl convertAssert(AssertStatement e) { - Expression message = e.getMessage(); - AssertStatementTreeImpl t = new AssertStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameassert), - convertExpression(e.getExpression()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - if (message != null) { - t.complete(firstTokenBefore(message, TerminalToken.TokenNameCOLON), convertExpression(message)); - } - return t; - } - - private SwitchStatementTreeImpl convertSwitchStatement(SwitchStatement e) { - Expression expression = e.getExpression(); - return new SwitchStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameswitch), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - firstTokenAfter(expression, TerminalToken.TokenNameLBRACE), - convertSwitchStatements(e.statements()), - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private List convertSwitchStatements(List list) { - List groups = new ArrayList<>(); - List caselabels = null; - StatementListTreeImpl body = null; - - for (Object o : list) { - if (o instanceof SwitchCase c) { - if (caselabels == null) { - caselabels = new ArrayList<>(); - body = StatementListTreeImpl.emptyList(); - } - - List expressions = ((List) c.expressions()).stream() - .map(Expression.class::cast) - .map(this::convertExpressionFromCase) - .toList(); - - caselabels.add(new CaseLabelTreeImpl( - firstTokenIn(c, c.isDefault() ? TerminalToken.TokenNamedefault : TerminalToken.TokenNamecase), - expressions, - lastTokenIn(c, /* TerminalToken.TokenNameCOLON or TerminalToken.TokenNameARROW */ ANY_TOKEN) - )); - } else { - if (caselabels != null) { - groups.add(new CaseGroupTreeImpl(caselabels, body)); - } - caselabels = null; - addStatementToList((Statement) o, Objects.requireNonNull(body)); - } - } - if (caselabels != null) { - groups.add(new CaseGroupTreeImpl(caselabels, body)); - } - return groups; - } - - private ExpressionTree convertExpressionFromCase(Expression e) { - if (e.getNodeType() == ASTNode.CASE_DEFAULT_EXPRESSION) { - return new DefaultPatternTreeImpl(firstTokenIn(e, TerminalToken.TokenNamedefault), e.resolveTypeBinding()); - } - if (e.getNodeType() == ASTNode.NULL_LITERAL) { - return new NullPatternTreeImpl((LiteralTreeImpl) convertExpression(e)); - } - if (e instanceof Pattern pattern) { - return convertPattern(pattern); - } - return convertExpression(e); - } - - private PatternTree convertPattern(Pattern p) { - switch (p.getNodeType()) { - case ASTNode.TYPE_PATTERN: - TypePattern typePattern = (TypePattern) p; - return new TypePatternTreeImpl(convertVariable(typePattern), typePattern.resolveTypeBinding()); - case ASTNode.RECORD_PATTERN: - RecordPattern recordPattern = (RecordPattern) p; - List nestedPatterns = recordPattern.patterns().stream() - .map(this::convertPattern) - .toList(); - - TypeTree patternType = convertType(recordPattern.getPatternType()); - var openParenToken = firstTokenIn(recordPattern, TerminalToken.TokenNameLPAREN); - var closeParenToken = lastTokenIn(recordPattern, TerminalToken.TokenNameRPAREN); - return new RecordPatternTreeImpl(patternType, openParenToken, nestedPatterns, closeParenToken); - case ASTNode.GUARDED_PATTERN: - GuardedPattern g = (GuardedPattern) p; - return new GuardedPatternTreeImpl( - convertPattern(g.getPattern()), - firstTokenBefore(g.getExpression(), TerminalToken.TokenNameRestrictedIdentifierWhen), - convertExpression(g.getExpression()), - g.resolveTypeBinding()); - case ASTNode.NULL_PATTERN: - // It is not clear how to reach this one, it seems to be possible only with badly constructed AST - // fall-through. Do nothing for now. - default: - // JEP-405 (not released as part of any JDK yet): ArrayPattern, RecordPattern - throw new IllegalStateException(ASTNode.nodeClassForType(p.getNodeType()).toString()); - } - } - - private SynchronizedStatementTreeImpl convertSynchronized(SynchronizedStatement e) { - Expression expression = e.getExpression(); - return new SynchronizedStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamesynchronized), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - convertBlock(e.getBody()) - ); - } - - private ExpressionStatementTreeImpl convertExpressionStatement(ExpressionStatement e) { - return new ExpressionStatementTreeImpl( - convertExpression(e.getExpression()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private ContinueStatementTreeImpl convertContinue(ContinueStatement e) { - SimpleName label = e.getLabel(); - IdentifierTreeImpl i = label == null ? null : createSimpleName(label); - usageLabel(i); - return new ContinueStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamecontinue), - i, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private LabeledStatementTreeImpl convertLabel(LabeledStatement e) { - IdentifierTreeImpl i = createSimpleName(e.getLabel()); - - JLabelSymbol symbol = new JLabelSymbol(i.name()); - labels.push(symbol); - - LabeledStatementTreeImpl t = new LabeledStatementTreeImpl( - i, - firstTokenAfter(e.getLabel(), TerminalToken.TokenNameCOLON), - createStatement(e.getBody()) - ); - - labels.pop(); - symbol.declaration = t; - t.labelSymbol = symbol; - return t; - } - - private ForEachStatementImpl convertForeach(EnhancedForStatement e) { - SingleVariableDeclaration parameter = e.getParameter(); - Expression expression = e.getExpression(); - return new ForEachStatementImpl( - firstTokenIn(e, TerminalToken.TokenNamefor), - firstTokenBefore(parameter, TerminalToken.TokenNameLPAREN), - convertVariable(parameter), - firstTokenAfter(parameter, TerminalToken.TokenNameCOLON), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - createStatement(e.getBody()) - ); - } - - private ThrowStatementTreeImpl convertThrow(ThrowStatement e) { - return new ThrowStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamethrow), - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameSEMICOLON) - ); - } - - private TryStatementTreeImpl convertTry(TryStatement e) { - ResourceListTreeImpl resources = convertResources(e); - List catches = convertCatchClauses(e); - - Block f = e.getFinally(); - return new TryStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNametry), - resources.isEmpty() ? null : firstTokenIn(e, TerminalToken.TokenNameLPAREN), - resources, - resources.isEmpty() ? null : firstTokenBefore(e.getBody(), TerminalToken.TokenNameRPAREN), - convertBlock(e.getBody()), - catches, - f == null ? null : firstTokenBefore(f, TerminalToken.TokenNamefinally), - f == null ? null : convertBlock(f) - ); - } - - private ResourceListTreeImpl convertResources(TryStatement e) { - List r = e.resources(); - ResourceListTreeImpl resources = ResourceListTreeImpl.emptyList(); - for (int i = 0; i < r.size(); i++) { - Expression o = (Expression) r.get(i); - if (ASTNode.VARIABLE_DECLARATION_EXPRESSION == o.getNodeType()) { - addVariableToList((VariableDeclarationExpression) o, resources); - } else { - resources.add(convertExpression(o)); - } - addSeparatorToList(e, o, resources.separators(), i < e.resources().size() - 1); - } - return resources; - } - - private void addSeparatorToList(TryStatement tryStatement, Expression resource, List separators, boolean isLast) { - if (isLast) { - separators.add(firstTokenAfter(resource, TerminalToken.TokenNameSEMICOLON)); - } else { - int tokenIndex = tokenManager.firstIndexBefore(tryStatement.getBody(), TerminalToken.TokenNameRPAREN); - while (true) { - Token token; - do { - tokenIndex--; - token = tokenManager.get(tokenIndex); - } while (isComment(token)); - - if (token.tokenType != TerminalToken.TokenNameSEMICOLON) { - break; - } - separators.add(createSyntaxToken(tokenIndex)); - } - } - } - - private List convertCatchClauses(TryStatement e) { - List catches = new ArrayList<>(); - for (Object o : e.catchClauses()) { - CatchClause c = (CatchClause) o; - catches.add(new CatchTreeImpl( - firstTokenIn(c, TerminalToken.TokenNamecatch), - firstTokenBefore(c.getException(), TerminalToken.TokenNameLPAREN), - convertVariable(c.getException()), - firstTokenAfter(c.getException(), TerminalToken.TokenNameRPAREN), - convertBlock(c.getBody()) - )); - } - return catches; - } - - private ExpressionStatementTreeImpl convertConstructorInvocation(ConstructorInvocation e) { - ArgumentListTreeImpl arguments = convertArguments( - e.arguments().isEmpty() ? lastTokenIn(e, TerminalToken.TokenNameLPAREN) : firstTokenBefore((ASTNode) e.arguments().get(0), TerminalToken.TokenNameLPAREN), - e.arguments(), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - - IdentifierTreeImpl i = new IdentifierTreeImpl(e.arguments().isEmpty() - ? lastTokenIn(e, TerminalToken.TokenNamethis) - : firstTokenBefore((ASTNode) e.arguments().get(0), TerminalToken.TokenNamethis)); - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - i, - convertTypeArguments(e.typeArguments()), - arguments - ); - t.methodBinding = e.resolveConstructorBinding(); - if (t.methodBinding != null) { - t.typeBinding = t.methodBinding.getDeclaringClass(); - t.methodBinding = excludeRecovery(t.methodBinding, arguments.size()); - } - i.binding = t.methodBinding; - usage(i.binding, i); - return new ExpressionStatementTreeImpl( - t, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private ExpressionStatementTreeImpl convertSuperConstructorInvocation(SuperConstructorInvocation e) { - IdentifierTreeImpl i = new IdentifierTreeImpl(firstTokenIn(e, TerminalToken.TokenNamesuper)); - ExpressionTree methodSelect = i; - if (e.getExpression() != null) { - methodSelect = new MemberSelectExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT), - i - ); - } - - ArgumentListTreeImpl arguments = convertArguments( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - e.arguments(), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - methodSelect, - convertTypeArguments(e.typeArguments()), - arguments - ); - t.methodBinding = e.resolveConstructorBinding(); - if (t.methodBinding != null) { - t.typeBinding = t.methodBinding.getDeclaringClass(); - t.methodBinding = excludeRecovery(t.methodBinding, arguments.size()); - } - i.binding = t.methodBinding; - usage(i.binding, i); - return new ExpressionStatementTreeImpl( - t, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private YieldStatementTreeImpl convertYield(YieldStatement e) { - InternalSyntaxToken yieldKeyword = null; - if (!e.isImplicit()) { - yieldKeyword = firstTokenIn(e, TerminalToken.TokenNameRestrictedIdentifierYield); - } - return new YieldStatementTreeImpl( - yieldKeyword, - convertExpression(e.getExpression()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private ExpressionTree convertExpression(Expression node) { - ExpressionTree t = createExpression(node); - ((AbstractTypedTree) t).typeBinding = node.resolveTypeBinding(); - return t; - } - - private ExpressionTree createExpression(Expression node) { - switch (node.getNodeType()) { - case ASTNode.SIMPLE_NAME: - return convertSimpleName((SimpleName) node); - case ASTNode.QUALIFIED_NAME: - return convertQualifiedName((QualifiedName) node); - case ASTNode.FIELD_ACCESS: - return convertFieldAccess((FieldAccess) node); - case ASTNode.SUPER_FIELD_ACCESS: - return convertFieldAccess((SuperFieldAccess) node); - case ASTNode.THIS_EXPRESSION: - return convertThisExpression((ThisExpression) node); - case ASTNode.ARRAY_ACCESS: - return convertArrayAccess((ArrayAccess) node); - case ASTNode.ARRAY_CREATION: - return convertArrayCreation((ArrayCreation) node); - case ASTNode.ARRAY_INITIALIZER: - return convertArrayInitializer((ArrayInitializer) node); - case ASTNode.ASSIGNMENT: - return convertAssignment((Assignment) node); - case ASTNode.CAST_EXPRESSION: - return convertTypeCastExpression((CastExpression) node); - case ASTNode.CLASS_INSTANCE_CREATION: - return convertClassInstanceCreation((ClassInstanceCreation) node); - case ASTNode.CONDITIONAL_EXPRESSION: - return convertConditionalExpression((ConditionalExpression) node); - case ASTNode.INFIX_EXPRESSION: - return convertInfixExpression((InfixExpression) node); - case ASTNode.METHOD_INVOCATION: - return convertMethodInvocation((MethodInvocation) node); - case ASTNode.SUPER_METHOD_INVOCATION: - return convertMethodInvocation((SuperMethodInvocation) node); - case ASTNode.PARENTHESIZED_EXPRESSION: - return convertParenthesizedExpression((ParenthesizedExpression) node); - case ASTNode.POSTFIX_EXPRESSION: - return convertPostfixExpression((PostfixExpression) node); - case ASTNode.PREFIX_EXPRESSION: - return convertPrefixExpression((PrefixExpression) node); - case ASTNode.INSTANCEOF_EXPRESSION: - return convertInstanceOf((InstanceofExpression) node); - case ASTNode.PATTERN_INSTANCEOF_EXPRESSION: - return convertInstanceOf((PatternInstanceofExpression) node); - case ASTNode.LAMBDA_EXPRESSION: - return convertLambdaExpression((LambdaExpression) node); - case ASTNode.CREATION_REFERENCE: - return convertMethodReference((CreationReference) node); - case ASTNode.EXPRESSION_METHOD_REFERENCE: - return convertMethodReference((ExpressionMethodReference) node); - case ASTNode.TYPE_METHOD_REFERENCE: - return convertMethodReference((TypeMethodReference) node); - case ASTNode.SUPER_METHOD_REFERENCE: - return convertMethodReference((SuperMethodReference) node); - case ASTNode.SWITCH_EXPRESSION: - return convertSwitchExpression((SwitchExpression) node); - case ASTNode.TYPE_LITERAL: - return convertTypeLiteral((TypeLiteral) node); - case ASTNode.NULL_LITERAL: - return convertLiteral((NullLiteral) node); - case ASTNode.NUMBER_LITERAL: - return convertLiteral((NumberLiteral) node); - case ASTNode.CHARACTER_LITERAL: - return convertLiteral((CharacterLiteral) node); - case ASTNode.BOOLEAN_LITERAL: - return convertLiteral((BooleanLiteral) node); - case ASTNode.STRING_LITERAL: - return convertLiteral((StringLiteral) node); - case ASTNode.TEXT_BLOCK: - return convertTextBlock((TextBlock) node); - case ASTNode.NORMAL_ANNOTATION, - ASTNode.MARKER_ANNOTATION, - ASTNode.SINGLE_MEMBER_ANNOTATION: - return convertAnnotation((Annotation) node); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - private IdentifierTreeImpl convertSimpleName(SimpleName e) { - IdentifierTreeImpl t = createSimpleName(e); - usage(t.binding, t); - return t; - } - - private MemberSelectExpressionTreeImpl convertQualifiedName(QualifiedName e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(rhs.binding, rhs); - return new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - rhs - ); - } - - private MemberSelectExpressionTreeImpl convertFieldAccess(FieldAccess e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(rhs.binding, rhs); - return new MemberSelectExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT), - rhs - ); - } - - private MemberSelectExpressionTreeImpl convertFieldAccess(SuperFieldAccess e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(rhs.binding, rhs); - if (e.getQualifier() == null) { - // super.name - return new MemberSelectExpressionTreeImpl( - unqualifiedKeywordSuper(e), - firstTokenIn(e, TerminalToken.TokenNameDOT), - rhs - ); - } - // qualifier.super.name - AbstractTypedTree qualifier = (AbstractTypedTree) convertExpression(e.getQualifier()); - KeywordSuper keywordSuper = new KeywordSuper(firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamesuper), null); - MemberSelectExpressionTreeImpl qualifiedSuper = new MemberSelectExpressionTreeImpl( - (ExpressionTree) qualifier, - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - keywordSuper - ); - if (qualifier.typeBinding != null) { - keywordSuper.typeBinding = qualifier.typeBinding; - qualifiedSuper.typeBinding = keywordSuper.typeBinding.getSuperclass(); - } - return new MemberSelectExpressionTreeImpl( - qualifiedSuper, - firstTokenBefore(e.getName(), TerminalToken.TokenNameDOT), - rhs - ); - } - - private ExpressionTree convertThisExpression(ThisExpression e) { - if (e.getQualifier() == null) { - return new KeywordThis(firstTokenIn(e, TerminalToken.TokenNamethis), null); - } - KeywordThis keywordThis = new KeywordThis( - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamethis), - e.resolveTypeBinding() - ); - return new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - keywordThis - ); - } - - private MemberSelectExpressionTreeImpl convertTypeLiteral(TypeLiteral e) { - return new MemberSelectExpressionTreeImpl( - (ExpressionTree) convertType(e.getType()), - lastTokenIn(e, TerminalToken.TokenNameDOT), - new IdentifierTreeImpl( - lastTokenIn(e, TerminalToken.TokenNameclass) - ) - ); - } - - private ArrayAccessExpressionTreeImpl convertArrayAccess(ArrayAccess e) { - Expression index = e.getIndex(); - return new ArrayAccessExpressionTreeImpl( - convertExpression(e.getArray()), - new ArrayDimensionTreeImpl( - firstTokenBefore(index, TerminalToken.TokenNameLBRACKET), - convertExpression(index), - firstTokenAfter(index, TerminalToken.TokenNameRBRACKET) - ) - ); - } - - private NewArrayTreeImpl convertArrayCreation(ArrayCreation e) { - List dimensions = ((List) e.dimensions()).stream() - .map(Expression.class::cast) - .map(dimension -> new ArrayDimensionTreeImpl( - firstTokenBefore(dimension, TerminalToken.TokenNameLBRACKET), - convertExpression(dimension), - firstTokenAfter(dimension, TerminalToken.TokenNameRBRACKET) - )) - .collect(Collectors.toCollection(ArrayList::new)); - InitializerListTreeImpl initializers = InitializerListTreeImpl.emptyList(); - if (e.getInitializer() != null) { - assert dimensions.isEmpty(); - - TypeTree type = convertType(e.getType()); - while (type.is(Tree.Kind.ARRAY_TYPE)) { - ArrayTypeTree arrayType = (ArrayTypeTree) type; - ArrayDimensionTreeImpl dimension = new ArrayDimensionTreeImpl( - arrayType.openBracketToken(), - null, - arrayType.closeBracketToken() - ).completeAnnotations(arrayType.annotations()); - dimensions.add(/* TODO suboptimal */ 0, dimension); - type = arrayType.type(); - } - - return ((NewArrayTreeImpl) convertExpression(e.getInitializer())) - .completeWithNewKeyword(firstTokenIn(e, TerminalToken.TokenNamenew)) - .complete(type) - .completeDimensions(dimensions); - } - TypeTree type = convertType(e.getType()); - int index = dimensions.size() - 1; - while (type.is(Tree.Kind.ARRAY_TYPE)) { - if (!type.annotations().isEmpty()) { - ((ArrayDimensionTreeImpl) dimensions.get(index)) - .completeAnnotations(type.annotations()); - } - index--; - type = ((ArrayTypeTree) type).type(); - } - - return new NewArrayTreeImpl(dimensions, initializers) - .complete(type) - .completeWithNewKeyword(firstTokenIn(e, TerminalToken.TokenNamenew)); - } - - private NewArrayTreeImpl convertArrayInitializer(ArrayInitializer e) { - InitializerListTreeImpl initializers = InitializerListTreeImpl.emptyList(); - for (int i = 0; i < e.expressions().size(); i++) { - Expression o = (Expression) e.expressions().get(i); - initializers.add(convertExpression(o)); - final int commaTokenIndex = firstTokenIndexAfter(o); - if (tokenManager.get(commaTokenIndex).tokenType == TerminalToken.TokenNameCOMMA) { - initializers.separators().add(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - } - return new NewArrayTreeImpl(Collections.emptyList(),initializers) - .completeWithCurlyBraces( - firstTokenIn(e, TerminalToken.TokenNameLBRACE), - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private AssignmentExpressionTreeImpl convertAssignment(Assignment e) { - Op op = operators.get(e.getOperator()); - return new AssignmentExpressionTreeImpl( - op.kind, - convertExpression(e.getLeftHandSide()), - firstTokenAfter(e.getLeftHandSide(), op.tokenType), - convertExpression(e.getRightHandSide()) - ); - } - - private TypeCastExpressionTreeImpl convertTypeCastExpression(CastExpression e) { - Type type = e.getType(); - if (type.getNodeType() == ASTNode.INTERSECTION_TYPE) { - List intersectionTypes = ((IntersectionType) type).types(); - QualifiedIdentifierListTreeImpl bounds = QualifiedIdentifierListTreeImpl.emptyList(); - for (int i = 1; i < intersectionTypes.size(); i++) { - Type o = (Type) intersectionTypes.get(i); - bounds.add(convertType(o)); - if (i < intersectionTypes.size() - 1) { - bounds.separators().add(firstTokenAfter(o, TerminalToken.TokenNameAND)); - } - } - return new TypeCastExpressionTreeImpl( - firstTokenBefore(type, TerminalToken.TokenNameLPAREN), - convertType((Type) intersectionTypes.get(0)), - firstTokenAfter((Type) intersectionTypes.get(0), TerminalToken.TokenNameAND), - bounds, - firstTokenAfter(type, TerminalToken.TokenNameRPAREN), - convertExpression(e.getExpression()) - ); - } - return new TypeCastExpressionTreeImpl( - firstTokenBefore(type, TerminalToken.TokenNameLPAREN), - convertType(type), - firstTokenAfter(type, TerminalToken.TokenNameRPAREN), - convertExpression(e.getExpression()) - ); - } - - private NewClassTreeImpl convertClassInstanceCreation(ClassInstanceCreation e) { - ArgumentListTreeImpl arguments = convertArguments( - firstTokenAfter(e.getType(), TerminalToken.TokenNameLPAREN), - e.arguments(), - firstTokenAfter(e.arguments().isEmpty() ? e.getType() : (ASTNode) e.arguments().get(e.arguments().size() - 1), TerminalToken.TokenNameRPAREN) - ); - - ClassTreeImpl classBody = null; - if (e.getAnonymousClassDeclaration() != null) { - List members = new ArrayList<>(); - for (Object o : e.getAnonymousClassDeclaration().bodyDeclarations()) { - processBodyDeclaration((BodyDeclaration) o, members); - } - classBody = new ClassTreeImpl( - Tree.Kind.CLASS, - firstTokenIn(e.getAnonymousClassDeclaration(), TerminalToken.TokenNameLBRACE), - members, - lastTokenIn(e.getAnonymousClassDeclaration(), TerminalToken.TokenNameRBRACE) - ); - classBody.typeBinding = e.getAnonymousClassDeclaration().resolveBinding(); - declaration(classBody.typeBinding, classBody); - } - - NewClassTreeImpl t = new NewClassTreeImpl( - convertType(e.getType()), - arguments, - classBody - ).completeWithNewKeyword( - e.getExpression() == null ? firstTokenIn(e, TerminalToken.TokenNamenew) : firstTokenAfter(e.getExpression(), TerminalToken.TokenNamenew) - ).completeWithTypeArguments( - convertTypeArguments(e.typeArguments()) - ); - if (e.getExpression() != null) { - t.completeWithEnclosingExpression(convertExpression(e.getExpression())); - t.completeWithDotToken(firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT)); - } - - IdentifierTreeImpl i = (IdentifierTreeImpl) t.getConstructorIdentifier(); - int nbArguments = arguments.size(); - if (e.getAnonymousClassDeclaration() == null) { - i.binding = excludeRecovery(e.resolveConstructorBinding(), nbArguments); - } else { - i.binding = excludeRecovery(findConstructorForAnonymousClass(e.getAST(), i.typeBinding, e.resolveConstructorBinding()), nbArguments); - } - usage(i.binding, i); - - return t; - } - - private ConditionalExpressionTreeImpl convertConditionalExpression(ConditionalExpression e) { - return new ConditionalExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameQUESTION), - convertExpression(e.getThenExpression()), - firstTokenAfter(e.getThenExpression(), TerminalToken.TokenNameCOLON), - convertExpression(e.getElseExpression()) - ); - } - - private BinaryExpressionTreeImpl convertInfixExpression(InfixExpression e) { - Op op = operators.get(e.getOperator()); - BinaryExpressionTreeImpl t = new BinaryExpressionTreeImpl( - op.kind, - convertExpression(e.getLeftOperand()), - firstTokenAfter(e.getLeftOperand(), op.tokenType), - convertExpression(e.getRightOperand()) - ); - for (Object o : e.extendedOperands()) { - Expression e2 = (Expression) o; - t.typeBinding = e.resolveTypeBinding(); - t = new BinaryExpressionTreeImpl( - op.kind, - t, - firstTokenBefore(e2, op.tokenType), - convertExpression(e2) - ); - } - return t; - } - - private MethodInvocationTreeImpl convertMethodInvocation(MethodInvocation e) { - ArgumentListTreeImpl arguments = convertArguments( - firstTokenAfter(e.getName(), TerminalToken.TokenNameLPAREN), - e.arguments(), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - ExpressionTree memberSelect; - if (e.getExpression() == null) { - memberSelect = rhs; - } else { - memberSelect = new MemberSelectExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT), - rhs - ); - } - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - memberSelect, - convertTypeArguments(e.typeArguments()), - arguments - ); - t.methodBinding = excludeRecovery(e.resolveMethodBinding(), arguments.size()); - rhs.binding = t.methodBinding; - usage(rhs.binding, rhs); - return t; - } - - private MethodInvocationTreeImpl convertMethodInvocation(SuperMethodInvocation e) { - ArgumentListTreeImpl arguments = convertArguments( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - e.arguments(), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - - ExpressionTree outermostSelect; - if (e.getQualifier() == null) { - outermostSelect = new MemberSelectExpressionTreeImpl( - unqualifiedKeywordSuper(e), - firstTokenIn(e, TerminalToken.TokenNameDOT), - rhs - ); - } else { - final int firstDotTokenIndex = tokenManager.firstIndexAfter(e.getQualifier(), TerminalToken.TokenNameDOT); - AbstractTypedTree qualifier = (AbstractTypedTree) convertExpression(e.getQualifier()); - KeywordSuper keywordSuper = new KeywordSuper(firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamesuper), null); - MemberSelectExpressionTreeImpl qualifiedSuper = new MemberSelectExpressionTreeImpl( - (ExpressionTree) qualifier, - createSyntaxToken(firstDotTokenIndex), - keywordSuper - ); - if (qualifier.typeBinding != null) { - keywordSuper.typeBinding = qualifier.typeBinding; - qualifiedSuper.typeBinding = keywordSuper.typeBinding.getSuperclass(); - } - outermostSelect = new MemberSelectExpressionTreeImpl( - qualifiedSuper, - createSyntaxToken(nextTokenIndex(firstDotTokenIndex, TerminalToken.TokenNameDOT)), - rhs - ); - } - - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - outermostSelect, - null, - arguments - ); - t.methodBinding = excludeRecovery(e.resolveMethodBinding(), arguments.size()); - rhs.binding = t.methodBinding; - usage(rhs.binding, rhs); - return t; - } - - private ParenthesizedTreeImpl convertParenthesizedExpression(ParenthesizedExpression e) { - return new ParenthesizedTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameRPAREN) - ); - } - - private InternalPostfixUnaryExpression convertPostfixExpression(PostfixExpression e) { - Op op = operators.get(e.getOperator()); - return new InternalPostfixUnaryExpression( - op.kind, - convertExpression(e.getOperand()), - firstTokenAfter(e.getOperand(), op.tokenType) - ); - } - - private InternalPrefixUnaryExpression convertPrefixExpression(PrefixExpression e) { - Op op = operators.get(e.getOperator()); - return new InternalPrefixUnaryExpression( - op.kind, - firstTokenIn(e, op.tokenType), - convertExpression(e.getOperand()) - ); - } - - private InstanceOfTreeImpl convertInstanceOf(InstanceofExpression e) { - Expression leftOperand = e.getLeftOperand(); - InternalSyntaxToken instanceofToken = firstTokenAfter(leftOperand, TerminalToken.TokenNameinstanceof); - return new InstanceOfTreeImpl(convertExpression(leftOperand), instanceofToken, convertType(e.getRightOperand())); + private JParser() { } - private InstanceOfTreeImpl convertInstanceOf(PatternInstanceofExpression e) { - Expression leftOperand = e.getLeftOperand(); - InternalSyntaxToken instanceofToken = firstTokenAfter(leftOperand, TerminalToken.TokenNameinstanceof); - return new InstanceOfTreeImpl(convertExpression(leftOperand), instanceofToken, convertPattern(e.getPattern())); - } - - private LambdaExpressionTreeImpl convertLambdaExpression(LambdaExpression e) { - List parameters = new ArrayList<>(); - for (int i = 0; i < e.parameters().size(); i++) { - VariableDeclaration o = (VariableDeclaration) e.parameters().get(i); - VariableTreeImpl t; - if (o.getNodeType() == ASTNode.VARIABLE_DECLARATION_FRAGMENT) { - t = new VariableTreeImpl(createSimpleName(o.getName())); - IVariableBinding variableBinding = o.resolveBinding(); - if (variableBinding != null) { - t.variableBinding = variableBinding; - ((InferedTypeTree) t.type()).typeBinding = variableBinding.getType(); - declaration(t.variableBinding, t); - } + public static JavaTree.CompilationUnitTreeImpl parse(String version, String unitName, String source) { + Objects.requireNonNull(version); + Objects.requireNonNull(unitName); + Objects.requireNonNull(source); + int line = 1; + int column = 0; + for (int i = 0; i < source.length(); i++) { + if (source.charAt(i) == '\n') { + line++; + column = 0; } else { - t = convertVariable((SingleVariableDeclaration) o); - } - parameters.add(t); - if (i < e.parameters().size() - 1) { - t.setEndToken(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - } - ASTNode body = e.getBody(); - var tree = new LambdaExpressionTreeImpl( - e.hasParentheses() ? firstTokenIn(e, TerminalToken.TokenNameLPAREN) : null, - parameters, - e.hasParentheses() ? firstTokenBefore(body, TerminalToken.TokenNameRPAREN) : null, - firstTokenBefore(body, TerminalToken.TokenNameARROW), - body.getNodeType() == ASTNode.BLOCK ? convertBlock((Block) body) : convertExpression((Expression) body) - ); - tree.methodBinding = e.resolveMethodBinding(); - return tree; - } - - private MethodReferenceTreeImpl convertMethodReference(CreationReference e) { - MethodReferenceTreeImpl t = new MethodReferenceTreeImpl( - convertType(e.getType()), - lastTokenIn(e, TerminalToken.TokenNameCOLON_COLON) - ); - IdentifierTreeImpl i = new IdentifierTreeImpl(lastTokenIn(e, TerminalToken.TokenNamenew)); - i.binding = e.resolveMethodBinding(); - usage(i.binding, i); - t.complete(convertTypeArguments(e.typeArguments()), i); - return t; - } - - private MethodReferenceTreeImpl convertMethodReference(ExpressionMethodReference e) { - MethodReferenceTreeImpl t = new MethodReferenceTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameCOLON_COLON) - ); - IdentifierTreeImpl i = createSimpleName(e.getName()); - usage(i.binding, i); - t.complete(convertTypeArguments(e.typeArguments()), i); - return t; - } - - private MethodReferenceTreeImpl convertMethodReference(TypeMethodReference e) { - MethodReferenceTreeImpl t = new MethodReferenceTreeImpl( - convertType(e.getType()), - firstTokenAfter(e.getType(), TerminalToken.TokenNameCOLON_COLON) - ); - IdentifierTreeImpl i = createSimpleName(e.getName()); - usage(i.binding, i); - t.complete(convertTypeArguments(e.typeArguments()), i); - return t; - } - - private MethodReferenceTreeImpl convertMethodReference(SuperMethodReference e) { - MethodReferenceTreeImpl t; - if (e.getQualifier() != null) { - t = new MethodReferenceTreeImpl( - new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - unqualifiedKeywordSuper(e) - ), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameCOLON_COLON) - ); - } else { - t = new MethodReferenceTreeImpl( - unqualifiedKeywordSuper(e), - firstTokenIn(e, TerminalToken.TokenNameCOLON_COLON) - ); - } - IdentifierTreeImpl i = createSimpleName(e.getName()); - usage(i.binding, i); - t.complete(convertTypeArguments(e.typeArguments()), i); - return t; - } - - private SwitchExpressionTreeImpl convertSwitchExpression(SwitchExpression e) { - Expression expr = e.getExpression(); - return new SwitchExpressionTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameswitch), - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - convertExpression(expr), - firstTokenAfter(expr, TerminalToken.TokenNameRPAREN), - firstTokenAfter(expr, TerminalToken.TokenNameLBRACE), - convertSwitchStatements(e.statements()), - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private LiteralTreeImpl convertLiteral(NullLiteral e) { - return new LiteralTreeImpl(Tree.Kind.NULL_LITERAL, firstTokenIn(e, TerminalToken.TokenNamenull)); - } - - private ExpressionTree convertLiteral(NumberLiteral e) { - int tokenIndex = tokenManager.findIndex(e.getStartPosition(), ANY_TOKEN, true); - TerminalToken tokenType = tokenManager.get(tokenIndex).tokenType; - boolean unaryMinus = tokenType == TerminalToken.TokenNameMINUS; - if (unaryMinus) { - tokenIndex++; - tokenType = tokenManager.get(tokenIndex).tokenType; - } - ExpressionTree result; - switch (tokenType) { - case TokenNameIntegerLiteral: - result = new LiteralTreeImpl(Tree.Kind.INT_LITERAL, createSyntaxToken(tokenIndex)); - break; - case TokenNameLongLiteral: - result = new LiteralTreeImpl(Tree.Kind.LONG_LITERAL, createSyntaxToken(tokenIndex)); - break; - case TokenNameFloatingPointLiteral: - result = new LiteralTreeImpl(Tree.Kind.FLOAT_LITERAL, createSyntaxToken(tokenIndex)); - break; - case TokenNameDoubleLiteral: - result = new LiteralTreeImpl(Tree.Kind.DOUBLE_LITERAL, createSyntaxToken(tokenIndex)); - break; - default: - throw new IllegalStateException(); - } - ((LiteralTreeImpl) result).typeBinding = e.resolveTypeBinding(); - if (unaryMinus) { - return new InternalPrefixUnaryExpression(Tree.Kind.UNARY_MINUS, createSyntaxToken(tokenIndex - 1), result); - } - return result; - } - - private LiteralTreeImpl convertLiteral(CharacterLiteral e) { - return new LiteralTreeImpl(Tree.Kind.CHAR_LITERAL, firstTokenIn(e, TerminalToken.TokenNameCharacterLiteral)); - } - - private LiteralTreeImpl convertLiteral(BooleanLiteral e) { - InternalSyntaxToken value = firstTokenIn(e, e.booleanValue() ? TerminalToken.TokenNametrue : TerminalToken.TokenNamefalse); - return new LiteralTreeImpl(Tree.Kind.BOOLEAN_LITERAL, value); - } - - private LiteralTreeImpl convertLiteral(StringLiteral e) { - return new LiteralTreeImpl(Tree.Kind.STRING_LITERAL, firstTokenIn(e, TerminalToken.TokenNameStringLiteral)); - } - - private LiteralTreeImpl convertTextBlock(TextBlock e) { - return new LiteralTreeImpl(Tree.Kind.TEXT_BLOCK, firstTokenIn(e, TerminalToken.TokenNameTextBlock)); - } - - private AnnotationTreeImpl convertAnnotation(Annotation e) { - ArgumentListTreeImpl arguments = ArgumentListTreeImpl.emptyList(); - if (e.getNodeType() == ASTNode.SINGLE_MEMBER_ANNOTATION) { - arguments.add(convertExpression(((SingleMemberAnnotation) e).getValue())); - arguments.complete( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - } else if (e.getNodeType() == ASTNode.NORMAL_ANNOTATION) { - for (int i = 0; i < ((NormalAnnotation) e).values().size(); i++) { - MemberValuePair o = (MemberValuePair) ((NormalAnnotation) e).values().get(i); - arguments.add(new AssignmentExpressionTreeImpl( - Tree.Kind.ASSIGNMENT, - createSimpleName(o.getName()), - firstTokenAfter(o.getName(), TerminalToken.TokenNameEQUAL), - convertExpression(o.getValue()) - )); - if (i < ((NormalAnnotation) e).values().size() - 1) { - arguments.separators().add(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - } - arguments.complete( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - } - return new AnnotationTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameAT), - (TypeTree) convertExpression(e.getTypeName()), - arguments - ); - } - - private KeywordSuper unqualifiedKeywordSuper(ASTNode node) { - InternalSyntaxToken token = firstTokenIn(node, TerminalToken.TokenNamesuper); - do { - if (node instanceof AbstractTypeDeclaration abstractTypeDeclaration) { - return new KeywordSuper(token, abstractTypeDeclaration.resolveBinding()); - } - if (node instanceof AnonymousClassDeclaration anonymousClassDeclaration) { - return new KeywordSuper(token, anonymousClassDeclaration.resolveBinding()); - } - node = node.getParent(); - } while (true); - } - - private TypeTree convertType(Type node) { - switch (node.getNodeType()) { - case ASTNode.PRIMITIVE_TYPE: - return convertPrimitiveType((PrimitiveType) node); - case ASTNode.SIMPLE_TYPE: - return convertSimpleType((SimpleType) node); - case ASTNode.UNION_TYPE: - return convertUnionType((UnionType) node); - case ASTNode.ARRAY_TYPE: - return convertArrayType((ArrayType) node); - case ASTNode.PARAMETERIZED_TYPE: - return convertParameterizedType((ParameterizedType) node); - case ASTNode.QUALIFIED_TYPE: - return convertQualifiedType((QualifiedType) node); - case ASTNode.NAME_QUALIFIED_TYPE: - return convertNamedQualifiedType((NameQualifiedType) node); - case ASTNode.WILDCARD_TYPE: - return convertWildcardType((WildcardType) node); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - private JavaTree.PrimitiveTypeTreeImpl convertPrimitiveType(PrimitiveType e) { - final JavaTree.PrimitiveTypeTreeImpl t; - switch (e.getPrimitiveTypeCode().toString()) { - case "byte": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamebyte)); - break; - case "short": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNameshort)); - break; - case "char": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamechar)); - break; - case "int": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNameint)); - break; - case "long": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamelong)); - break; - case "float": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamefloat)); - break; - case "double": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamedouble)); - break; - case "boolean": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNameboolean)); - break; - case "void": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamevoid)); - break; - default: - throw new IllegalStateException(e.getPrimitiveTypeCode().toString()); - } - t.complete(convertAnnotations(e.annotations())); - t.typeBinding = e.resolveBinding(); - return t; - } - - private JavaTree.AnnotatedTypeTree convertSimpleType(SimpleType e) { - List annotations = ((List) e.annotations()).stream() - .map(Annotation.class::cast) - .map(annotation -> (AnnotationTree) convertExpression(annotation)) - .toList(); - JavaTree.AnnotatedTypeTree t = e.isVar() ? convertVarType(e) : (JavaTree.AnnotatedTypeTree) convertExpression(e.getName()); - t.complete(annotations); - // typeBinding is assigned by convertVarType or convertExpression - return t; - } - - private JavaTree.UnionTypeTreeImpl convertUnionType(UnionType e) { - QualifiedIdentifierListTreeImpl alternatives = QualifiedIdentifierListTreeImpl.emptyList(); - for (int i = 0; i < e.types().size(); i++) { - Type o = (Type) e.types().get(i); - alternatives.add(convertType(o)); - if (i < e.types().size() - 1) { - alternatives.separators().add(firstTokenAfter(o, TerminalToken.TokenNameOR)); - } - } - JavaTree.UnionTypeTreeImpl t = new JavaTree.UnionTypeTreeImpl(alternatives); - t.typeBinding = e.resolveBinding(); - return t; - } - - private TypeTree convertArrayType(ArrayType e) { - @Nullable ITypeBinding elementTypeBinding = e.getElementType().resolveBinding(); - TypeTree t = convertType(e.getElementType()); - int tokenIndex = tokenManager.firstIndexAfter(e.getElementType(), TerminalToken.TokenNameLBRACKET); - for (int i = 0; i < e.dimensions().size(); i++) { - if (i > 0) { - tokenIndex = nextTokenIndex(tokenIndex, TerminalToken.TokenNameLBRACKET); - } - t = new JavaTree.ArrayTypeTreeImpl( - t, - (List) convertAnnotations(((Dimension) e.dimensions().get(i)).annotations()), - createSyntaxToken(tokenIndex), - createSyntaxToken(nextTokenIndex(tokenIndex, TerminalToken.TokenNameRBRACKET)) - ); - if (elementTypeBinding != null) { - ((JavaTree.ArrayTypeTreeImpl) t).typeBinding = elementTypeBinding.createArrayType(i + 1); - } - } - return t; - } - - private JavaTree.ParameterizedTypeTreeImpl convertParameterizedType(ParameterizedType e) { - LineColumnConverter.Pos pos = lineColumnConverter.toPos(e.getStartPosition() + e.getLength() - 1); - JavaTree.ParameterizedTypeTreeImpl t = new JavaTree.ParameterizedTypeTreeImpl( - convertType(e.getType()), - convertTypeArguments( - firstTokenAfter(e.getType(), TerminalToken.TokenNameLESS), - e.typeArguments(), - new InternalSyntaxToken( - pos.line(), - pos.columnOffset(), - ">", - /* TODO */ Collections.emptyList(), - false - ) - ) - ); - t.typeBinding = e.resolveBinding(); - return t; - } - - private MemberSelectExpressionTreeImpl convertQualifiedType(QualifiedType e) { - MemberSelectExpressionTreeImpl t = new MemberSelectExpressionTreeImpl( - (ExpressionTree) convertType(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - createSimpleName(e.getName()) - ); - ((IdentifierTreeImpl) t.identifier()).complete(convertAnnotations(e.annotations())); - t.typeBinding = e.resolveBinding(); - return t; - } - - private MemberSelectExpressionTreeImpl convertNamedQualifiedType(NameQualifiedType e) { - MemberSelectExpressionTreeImpl t = new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - createSimpleName(e.getName()) - ); - ((IdentifierTreeImpl) t.identifier()).complete(convertAnnotations(e.annotations())); - t.typeBinding = e.resolveBinding(); - return t; - } - - private JavaTree.WildcardTreeImpl convertWildcardType(WildcardType e) { - final InternalSyntaxToken questionToken = e.annotations().isEmpty() - ? firstTokenIn(e, TerminalToken.TokenNameQUESTION) - : firstTokenAfter((ASTNode) e.annotations().get(e.annotations().size() - 1), TerminalToken.TokenNameQUESTION); - JavaTree.WildcardTreeImpl t; - Type bound = e.getBound(); - if (bound == null) { - t = new JavaTree.WildcardTreeImpl(questionToken); - } else { - boolean isUpperBound = e.isUpperBound(); - t = new JavaTree.WildcardTreeImpl( - isUpperBound ? Tree.Kind.EXTENDS_WILDCARD : Tree.Kind.SUPER_WILDCARD, - firstTokenBefore(bound, isUpperBound ? TerminalToken.TokenNameextends : TerminalToken.TokenNamesuper), - convertType(bound) - ).complete(questionToken); - } - t.complete(convertAnnotations(e.annotations())); - t.typeBinding = e.resolveBinding(); - return t; - } - - @Nullable - private static IMethodBinding excludeRecovery(@Nullable IMethodBinding methodBinding, int arguments) { - if (methodBinding == null) { - return null; - } - if (methodBinding.isVarargs()) { - if (arguments + 1 < methodBinding.getParameterTypes().length) { - return null; - } - } else { - if (arguments != methodBinding.getParameterTypes().length) { - return null; - } - } - return methodBinding; - } - - @Nullable - private static IMethodBinding findConstructorForAnonymousClass(AST ast, @Nullable ITypeBinding typeBinding, @Nullable IMethodBinding methodBinding) { - if (typeBinding == null || methodBinding == null) { - return null; - } - if (typeBinding.isInterface()) { - typeBinding = ast.resolveWellKnownType("java.lang.Object"); - } - return Stream.of(typeBinding.getDeclaredMethods()) - .filter(methodBinding::isSubsignature) - .findFirst() - .orElse(null); - } - - private List convertAnnotations(List e) { - return e.stream() - .map(o -> (AnnotationTree) convertExpression((Annotation) o)) - .toList(); - } - - private ModifiersTreeImpl convertModifiers(List source) { - return new ModifiersTreeImpl(source.stream() - .map(o -> convertModifier((IExtendedModifier) o)) - .toList()); - } - - private ModifierTree convertModifier(IExtendedModifier node) { - switch (((ASTNode) node).getNodeType()) { - case ASTNode.NORMAL_ANNOTATION, - ASTNode.MARKER_ANNOTATION, - ASTNode.SINGLE_MEMBER_ANNOTATION: - return (AnnotationTree) convertExpression((Expression) node); - case ASTNode.MODIFIER: - return convertModifier((org.eclipse.jdt.core.dom.Modifier) node); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(((ASTNode) node).getNodeType()).toString()); - } - } - - private ModifierTree convertModifier(org.eclipse.jdt.core.dom.Modifier node) { - switch (node.getKeyword().toString()) { - case "public": - return new ModifierKeywordTreeImpl(Modifier.PUBLIC, firstTokenIn(node, TerminalToken.TokenNamepublic)); - case "protected": - return new ModifierKeywordTreeImpl(Modifier.PROTECTED, firstTokenIn(node, TerminalToken.TokenNameprotected)); - case "private": - return new ModifierKeywordTreeImpl(Modifier.PRIVATE, firstTokenIn(node, TerminalToken.TokenNameprivate)); - case "static": - return new ModifierKeywordTreeImpl(Modifier.STATIC, firstTokenIn(node, TerminalToken.TokenNamestatic)); - case "abstract": - return new ModifierKeywordTreeImpl(Modifier.ABSTRACT, firstTokenIn(node, TerminalToken.TokenNameabstract)); - case "final": - return new ModifierKeywordTreeImpl(Modifier.FINAL, firstTokenIn(node, TerminalToken.TokenNamefinal)); - case "native": - return new ModifierKeywordTreeImpl(Modifier.NATIVE, firstTokenIn(node, TerminalToken.TokenNamenative)); - case "synchronized": - return new ModifierKeywordTreeImpl(Modifier.SYNCHRONIZED, firstTokenIn(node, TerminalToken.TokenNamesynchronized)); - case "transient": - return new ModifierKeywordTreeImpl(Modifier.TRANSIENT, firstTokenIn(node, TerminalToken.TokenNametransient)); - case "volatile": - return new ModifierKeywordTreeImpl(Modifier.VOLATILE, firstTokenIn(node, TerminalToken.TokenNamevolatile)); - case "strictfp": - return new ModifierKeywordTreeImpl(Modifier.STRICTFP, firstTokenIn(node, TerminalToken.TokenNamestrictfp)); - case "default": - return new ModifierKeywordTreeImpl(Modifier.DEFAULT, firstTokenIn(node, TerminalToken.TokenNamedefault)); - case "sealed": - return new ModifierKeywordTreeImpl(Modifier.SEALED, firstTokenIn(node, TerminalToken.TokenNameRestrictedIdentifiersealed)); - case "non-sealed": { - return new ModifierKeywordTreeImpl(Modifier.NON_SEALED, firstTokenIn(node, TerminalToken.TokenNamenon_sealed)); + column++; } - default: - throw new IllegalStateException(node.getKeyword().toString()); - } - } - - private static final TerminalToken ANY_TOKEN = TerminalToken.TokenNameInvalid; - - private static final Map operators = new HashMap<>(); - - private static class Op { - final Tree.Kind kind; - - /** - * {@link TerminalToken} - */ - final TerminalToken tokenType; - - Op(Tree.Kind kind, TerminalToken tokenType) { - this.kind = kind; - this.tokenType = tokenType; } - } - - static { - operators.put(PrefixExpression.Operator.PLUS, new Op(Tree.Kind.UNARY_PLUS, TerminalToken.TokenNamePLUS)); - operators.put(PrefixExpression.Operator.MINUS, new Op(Tree.Kind.UNARY_MINUS, TerminalToken.TokenNameMINUS)); - operators.put(PrefixExpression.Operator.NOT, new Op(Tree.Kind.LOGICAL_COMPLEMENT, TerminalToken.TokenNameNOT)); - operators.put(PrefixExpression.Operator.COMPLEMENT, new Op(Tree.Kind.BITWISE_COMPLEMENT, TerminalToken.TokenNameTWIDDLE)); - operators.put(PrefixExpression.Operator.DECREMENT, new Op(Tree.Kind.PREFIX_DECREMENT, TerminalToken.TokenNameMINUS_MINUS)); - operators.put(PrefixExpression.Operator.INCREMENT, new Op(Tree.Kind.PREFIX_INCREMENT, TerminalToken.TokenNamePLUS_PLUS)); - - operators.put(PostfixExpression.Operator.DECREMENT, new Op(Tree.Kind.POSTFIX_DECREMENT, TerminalToken.TokenNameMINUS_MINUS)); - operators.put(PostfixExpression.Operator.INCREMENT, new Op(Tree.Kind.POSTFIX_INCREMENT, TerminalToken.TokenNamePLUS_PLUS)); - - operators.put(InfixExpression.Operator.TIMES, new Op(Tree.Kind.MULTIPLY, TerminalToken.TokenNameMULTIPLY)); - operators.put(InfixExpression.Operator.DIVIDE, new Op(Tree.Kind.DIVIDE, TerminalToken.TokenNameDIVIDE)); - operators.put(InfixExpression.Operator.REMAINDER, new Op(Tree.Kind.REMAINDER, TerminalToken.TokenNameREMAINDER)); - operators.put(InfixExpression.Operator.PLUS, new Op(Tree.Kind.PLUS, TerminalToken.TokenNamePLUS)); - operators.put(InfixExpression.Operator.MINUS, new Op(Tree.Kind.MINUS, TerminalToken.TokenNameMINUS)); - operators.put(InfixExpression.Operator.LEFT_SHIFT, new Op(Tree.Kind.LEFT_SHIFT, TerminalToken.TokenNameLEFT_SHIFT)); - operators.put(InfixExpression.Operator.RIGHT_SHIFT_SIGNED, new Op(Tree.Kind.RIGHT_SHIFT, TerminalToken.TokenNameRIGHT_SHIFT)); - operators.put(InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED, new Op(Tree.Kind.UNSIGNED_RIGHT_SHIFT, TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT)); - operators.put(InfixExpression.Operator.LESS, new Op(Tree.Kind.LESS_THAN, TerminalToken.TokenNameLESS)); - operators.put(InfixExpression.Operator.GREATER, new Op(Tree.Kind.GREATER_THAN, TerminalToken.TokenNameGREATER)); - operators.put(InfixExpression.Operator.LESS_EQUALS, new Op(Tree.Kind.LESS_THAN_OR_EQUAL_TO, TerminalToken.TokenNameLESS_EQUAL)); - operators.put(InfixExpression.Operator.GREATER_EQUALS, new Op(Tree.Kind.GREATER_THAN_OR_EQUAL_TO, TerminalToken.TokenNameGREATER_EQUAL)); - operators.put(InfixExpression.Operator.EQUALS, new Op(Tree.Kind.EQUAL_TO, TerminalToken.TokenNameEQUAL_EQUAL)); - operators.put(InfixExpression.Operator.NOT_EQUALS, new Op(Tree.Kind.NOT_EQUAL_TO, TerminalToken.TokenNameNOT_EQUAL)); - operators.put(InfixExpression.Operator.XOR, new Op(Tree.Kind.XOR, TerminalToken.TokenNameXOR)); - operators.put(InfixExpression.Operator.OR, new Op(Tree.Kind.OR, TerminalToken.TokenNameOR)); - operators.put(InfixExpression.Operator.AND, new Op(Tree.Kind.AND, TerminalToken.TokenNameAND)); - operators.put(InfixExpression.Operator.CONDITIONAL_OR, new Op(Tree.Kind.CONDITIONAL_OR, TerminalToken.TokenNameOR_OR)); - operators.put(InfixExpression.Operator.CONDITIONAL_AND, new Op(Tree.Kind.CONDITIONAL_AND, TerminalToken.TokenNameAND_AND)); - - operators.put(Assignment.Operator.ASSIGN, new Op(Tree.Kind.ASSIGNMENT, TerminalToken.TokenNameEQUAL)); - operators.put(Assignment.Operator.PLUS_ASSIGN, new Op(Tree.Kind.PLUS_ASSIGNMENT, TerminalToken.TokenNamePLUS_EQUAL)); - operators.put(Assignment.Operator.MINUS_ASSIGN, new Op(Tree.Kind.MINUS_ASSIGNMENT, TerminalToken.TokenNameMINUS_EQUAL)); - operators.put(Assignment.Operator.TIMES_ASSIGN, new Op(Tree.Kind.MULTIPLY_ASSIGNMENT, TerminalToken.TokenNameMULTIPLY_EQUAL)); - operators.put(Assignment.Operator.DIVIDE_ASSIGN, new Op(Tree.Kind.DIVIDE_ASSIGNMENT, TerminalToken.TokenNameDIVIDE_EQUAL)); - operators.put(Assignment.Operator.BIT_AND_ASSIGN, new Op(Tree.Kind.AND_ASSIGNMENT, TerminalToken.TokenNameAND_EQUAL)); - operators.put(Assignment.Operator.BIT_OR_ASSIGN, new Op(Tree.Kind.OR_ASSIGNMENT, TerminalToken.TokenNameOR_EQUAL)); - operators.put(Assignment.Operator.BIT_XOR_ASSIGN, new Op(Tree.Kind.XOR_ASSIGNMENT, TerminalToken.TokenNameXOR_EQUAL)); - operators.put(Assignment.Operator.REMAINDER_ASSIGN, new Op(Tree.Kind.REMAINDER_ASSIGNMENT, TerminalToken.TokenNameREMAINDER_EQUAL)); - operators.put(Assignment.Operator.LEFT_SHIFT_ASSIGN, new Op(Tree.Kind.LEFT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameLEFT_SHIFT_EQUAL)); - operators.put(Assignment.Operator.RIGHT_SHIFT_SIGNED_ASSIGN, new Op(Tree.Kind.RIGHT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameRIGHT_SHIFT_EQUAL)); - operators.put(Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN, new Op(Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL)); + InternalSyntaxToken eof = new InternalSyntaxToken(line, column, "", Collections.emptyList(), true); + JavaTree.CompilationUnitTreeImpl unit = new JavaTree.CompilationUnitTreeImpl(null, Collections.emptyList(), Collections.emptyList(), null, eof); + unit.sema = new JSema(); + return unit; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java b/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java index 79cffd79a90..6b6f0c90625 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java @@ -17,31 +17,16 @@ package org.sonar.java.model; import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; import java.util.stream.StreamSupport; -import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.FileASTRequestor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputFile; -import org.sonar.java.AnalysisException; import org.sonar.java.AnalysisProgress; import org.sonar.java.ExecutionTimeReport; -import org.sonar.java.ProgressMonitor; import org.sonar.java.annotations.VisibleForTesting; import org.sonar.plugins.java.api.JavaVersion; import org.sonarsource.analyzer.commons.ProgressReport; @@ -53,9 +38,6 @@ public abstract class JParserConfig { private static final Logger LOG = LoggerFactory.getLogger(JParserConfig.class); - private static final String MAXIMUM_ECJ_WARNINGS = "42000"; - private static final Set JRE_JARS = new HashSet<>(Arrays.asList("rt.jar", "jrt-fs.jar", "android.jar")); - final JavaVersion javaVersion; final List classpath; final boolean shouldIgnoreUnnamedModuleForSplitPackage; @@ -113,36 +95,6 @@ public JavaTree.CompilationUnitTreeImpl get() throws Exception { } } - public ASTParser astParser() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - Map options = new HashMap<>(JavaCore.getOptions()); - JavaCore.setComplianceOptions(javaVersion.effectiveJavaVersionAsString(), options); - options.put(JavaCore.COMPILER_PB_MAX_PER_UNIT, MAXIMUM_ECJ_WARNINGS); - if (shouldIgnoreUnnamedModuleForSplitPackage) { - options.put(JavaCore.COMPILER_IGNORE_UNNAMED_MODULE_FOR_SPLIT_PACKAGE, "enabled"); - } - if (shouldEnablePreviewFlag(javaVersion)) { - options.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled"); - } - // enabling all supported compiler warnings - JProblem.Type.compilerOptions() - .forEach(option -> options.put(option, "warning")); - - astParser.setCompilerOptions(options); - - boolean includeRunningVMBootclasspath = classpath.stream() - .noneMatch(f -> JRE_JARS.contains(f.getName())); - - astParser.setEnvironment(classpath.stream() - .map(File::getAbsolutePath) - .toArray(String[]::new), new String[] {}, new String[] {}, includeRunningVMBootclasspath); - - astParser.setResolveBindings(true); - astParser.setBindingsRecovery(true); - - return astParser; - } - @VisibleForTesting static class Batch extends JParserConfig { @@ -153,70 +105,20 @@ static class Batch extends JParserConfig { @Override public void parse(Iterable inputFiles, BooleanSupplier isCanceled, AnalysisProgress analysisProgress, BiConsumer action) { - - List sourceFilePaths = new ArrayList<>(); - Set notYetAnalyzedFiles = new LinkedHashSet<>(); - List encodings = new ArrayList<>(); - Map inputs = new HashMap<>(); - for (InputFile inputFile : inputFiles) { - String sourceFilePath = inputFile.absolutePath(); - inputs.put(new File(sourceFilePath), inputFile); - sourceFilePaths.add(sourceFilePath); - encodings.add(inputFile.charset().name()); - notYetAnalyzedFiles.add(inputFile); - } - ExecutionTimeReport executionTimeReport = new ExecutionTimeReport(); - ProgressMonitor monitor = new ProgressMonitor(isCanceled, analysisProgress); PerformanceMeasure.Duration batchPerformance = PerformanceMeasure.start("ParseAsBatch"); try { - astParser().createASTs(sourceFilePaths.toArray(new String[0]), encodings.toArray(new String[0]), new String[0], new FileASTRequestor() { - @Override - public void acceptAST(String sourceFilePath, CompilationUnit ast) { - PerformanceMeasure.Duration convertDuration = PerformanceMeasure.start("Convert"); - InputFile inputFile = inputs.get(new File(sourceFilePath)); - executionTimeReport.start(inputFile); - Result result; - try { - result = new Result(JParser.convert(javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents(), ast)); - } catch (Exception e) { - result = new Result(e); - } - convertDuration.stop(); - PerformanceMeasure.Duration analyzeDuration = PerformanceMeasure.start("Analyze"); - action.accept(inputFile, result); - - notYetAnalyzedFiles.remove(inputFile); - executionTimeReport.end(); - analyzeDuration.stop(); + for (InputFile inputFile : inputFiles) { + if (isCanceled.getAsBoolean()) { + break; } - }, monitor); - if (!notYetAnalyzedFiles.isEmpty()) { - String message = String.format("%d/%d files were not analyzed by the batch mode", notYetAnalyzedFiles.size(), sourceFilePaths.size()); - throw new AnalysisException(message); - } - } catch (OperationCanceledException e) { - throw e; - } catch (RuntimeException e) { - LOG.warn("Unexpected {}: {}", e.getClass().getSimpleName(), e.getMessage()); - if (!notYetAnalyzedFiles.isEmpty()) { - fallbackToFileByFileMode(notYetAnalyzedFiles.stream().toList(), isCanceled, action); + executionTimeReport.start(inputFile); + action.accept(inputFile, parseFile(inputFile)); + executionTimeReport.end(); } } finally { batchPerformance.stop(); - // ExecutionTimeReport will not include the parsing time by file when using batch mode. executionTimeReport.reportAsBatch(); - monitor.done(); - } - } - - private void fallbackToFileByFileMode(List inputFiles, BooleanSupplier isCanceled, BiConsumer action) { - LOG.warn("Fallback to file by file analysis for {} files", inputFiles.size()); - for (InputFile inputFile : inputFiles) { - if (isCanceled.getAsBoolean()) { - break; - } - FileByFile.parse(astParser(), inputFile, javaVersion, action); } } @@ -247,7 +149,7 @@ public void parse(Iterable inputFiles, BooleanSupplier isCa break; } executionTimeReport.start(inputFile); - parse(astParser(), inputFile, javaVersion, action); + action.accept(inputFile, parseFile(inputFile)); executionTimeReport.end(); progressReport.nextFile(); } @@ -261,18 +163,16 @@ public void parse(Iterable inputFiles, BooleanSupplier isCa executionTimeReport.report(); } } + } - private static void parse(ASTParser astParser, InputFile inputFile, JavaVersion javaVersion, BiConsumer action) { - Result result; - PerformanceMeasure.Duration parseDuration = PerformanceMeasure.start("JParser"); - try { - result = new Result(JParser.parse(astParser, javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents())); - } catch (Exception e) { - result = new Result(e); - } finally { - parseDuration.stop(); - } - action.accept(inputFile, result); + final Result parseFile(InputFile inputFile) { + PerformanceMeasure.Duration parseDuration = PerformanceMeasure.start("JParser"); + try { + return new Result(JParser.parse(javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents())); + } catch (Exception e) { + return new Result(e); + } finally { + parseDuration.stop(); } } @@ -280,6 +180,7 @@ private static void parse(ASTParser astParser, InputFile inputFile, JavaVersion static boolean shouldEnablePreviewFlag(JavaVersion currentVersion) { return currentVersion.arePreviewFeaturesEnabled(); } + @FunctionalInterface public interface ParserConfigConstructor { JParserConfig apply(JavaVersion version, List files, Boolean shouldIgnoreUnnamedModuleForSplitPackage); diff --git a/java-frontend/src/main/java/org/sonar/java/model/JProblem.java b/java-frontend/src/main/java/org/sonar/java/model/JProblem.java index 38abd459dad..e6102354e53 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JProblem.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JProblem.java @@ -21,8 +21,6 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Stream; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.compiler.IProblem; import org.sonar.plugins.java.api.tree.Tree; import org.sonarsource.analyzer.commons.collections.SetUtils; @@ -80,12 +78,12 @@ public int hashCode() { } public enum Type { - UNDEFINED_TYPE(IProblem.UndefinedType), - PREVIEW_FEATURE_USED(IProblem.PreviewFeatureUsed), - UNUSED_IMPORT(IProblem.UnusedImport, JavaCore.COMPILER_PB_UNUSED_IMPORT, Tree.Kind.IMPORT), - REDUNDANT_CAST(IProblem.UnnecessaryCast, JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK, Tree.Kind.TYPE_CAST, Tree.Kind.PARENTHESIZED_EXPRESSION), - ASSIGNMENT_HAS_NO_EFFECT(IProblem.AssignmentHasNoEffect, JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT, Tree.Kind.ASSIGNMENT), - MASKED_CATCH(IProblem.MaskedCatch, JavaCore.COMPILER_PB_HIDDEN_CATCH_BLOCK, Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT); + UNDEFINED_TYPE(16777218), + PREVIEW_FEATURE_USED(4195408), + UNUSED_IMPORT(268435844, "org.eclipse.jdt.core.compiler.problem.unusedImport", Tree.Kind.IMPORT), + REDUNDANT_CAST(553648309, "org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck", Tree.Kind.TYPE_CAST, Tree.Kind.PARENTHESIZED_EXPRESSION), + ASSIGNMENT_HAS_NO_EFFECT(536871090, "org.eclipse.jdt.core.compiler.problem.noEffectAssignment", Tree.Kind.ASSIGNMENT), + MASKED_CATCH(16777381, "org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock", Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT); private final int warningID; private final String compilerOptionKey; @@ -105,8 +103,8 @@ public enum Type { this.kinds = SetUtils.immutableSetOf(kinds); } - boolean matches(IProblem warning) { - return warning.getID() == warningID; + boolean matches(int problemId) { + return problemId == warningID; } public static Set compilerOptions() { diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSema.java b/java-frontend/src/main/java/org/sonar/java/model/JSema.java index 4045f8d1db5..53c95cb54f6 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSema.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSema.java @@ -16,178 +16,25 @@ */ package org.sonar.java.model; -import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Set; -import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTUtils; -import org.eclipse.jdt.core.dom.IAnnotationBinding; -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.sonar.plugins.java.api.semantic.Sema; -import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; -import org.sonar.plugins.java.api.tree.IdentifierTree; -import org.sonar.plugins.java.api.tree.Tree; public final class JSema implements Sema { - private final AST ast; - final Set undefinedTypes = new HashSet<>(); - final Map declarations = new HashMap<>(); - final Map> usages = new HashMap<>(); - private final Map types = new HashMap<>(); - private final Map symbols = new HashMap<>(); - private final Map initializerBlockSymbols = new HashMap<>(); - private final Map staticInitializerBlockSymbols = new HashMap<>(); - private final Map annotations = new HashMap<>(); - private final Map nameToTypeCache = new HashMap<>(); - - JSema(AST ast) { - this.ast = ast; - } - - public JType type(ITypeBinding typeBinding) { - JType cached = types.get(typeBinding); - if (cached != null) { - return cached; - } - return types.computeIfAbsent(typeBinding, k -> new JType(this, JType.normalize(k))); - } - - List types(ITypeBinding[] typeBindings) { - if (typeBindings.length == 0) { - return Collections.emptyList(); - } - Type[] result = new Type[typeBindings.length]; - for (int i = 0; i < typeBindings.length; i++) { - result[i] = type(typeBindings[i]); - } - return Arrays.asList(result); - } - - public Symbol packageSymbol(@Nullable IPackageBinding packageBinding) { - if (packageBinding == null) { - return Symbol.ROOT_PACKAGE; - } - JSymbol cached = symbols.get(packageBinding); - if (cached != null) { - return cached; - } - return symbols.computeIfAbsent(packageBinding, k -> new JPackageSymbol(this, (IPackageBinding) k)); - } - - public JTypeSymbol typeSymbol(ITypeBinding typeBinding) { - JTypeSymbol cached = (JTypeSymbol) symbols.get(typeBinding); - if (cached != null) { - return cached; - } - return (JTypeSymbol) symbols.computeIfAbsent(typeBinding, k -> new JTypeSymbol(this, JType.normalize((ITypeBinding) k))); - } - - public JMethodSymbol methodSymbol(IMethodBinding methodBinding) { - JMethodSymbol cached = (JMethodSymbol) symbols.get(methodBinding); - if (cached != null) { - return cached; - } - return (JMethodSymbol) symbols.computeIfAbsent(methodBinding, k -> new JMethodSymbol(this, (IMethodBinding) k)); - } - - public JInitializerBlockSymbol initializerBlockSymbol(JTypeSymbol owner) { - JInitializerBlockSymbol cached = initializerBlockSymbols.get(owner); - if (cached != null) { - return cached; - } - return initializerBlockSymbols.computeIfAbsent(owner, k -> new JInitializerBlockSymbol(k, false)); - } - - public JInitializerBlockSymbol staticInitializerBlockSymbol(JTypeSymbol owner) { - JInitializerBlockSymbol cached = staticInitializerBlockSymbols.get(owner); - if (cached != null) { - return cached; - } - return staticInitializerBlockSymbols.computeIfAbsent(owner, k -> new JInitializerBlockSymbol(k, true)); - } - - - public JVariableSymbol variableSymbol(IVariableBinding variableBinding) { - JVariableSymbol cached = (JVariableSymbol) symbols.get(variableBinding); - if (cached != null) { - return cached; - } - return (JVariableSymbol) symbols.computeIfAbsent(variableBinding, k -> new JVariableSymbol(this, (IVariableBinding) k)); - } - - JSymbolMetadata.JAnnotationInstance annotation(IAnnotationBinding annotationBinding) { - JSymbolMetadata.JAnnotationInstance cached = annotations.get(annotationBinding); - if (cached != null) { - return cached; - } - return annotations.computeIfAbsent(annotationBinding, k -> new JSymbolMetadata.JAnnotationInstance(this, k)); - } - - static IBinding declarationBinding(IBinding binding) { - switch (binding.getKind()) { - case IBinding.TYPE: - return ((ITypeBinding) binding).getTypeDeclaration(); - case IBinding.METHOD: - return ((IMethodBinding) binding).getMethodDeclaration(); - case IBinding.VARIABLE: - return ((IVariableBinding) binding).getVariableDeclaration(); - default: - return binding; - } - } - @Override public Type getClassType(String fullyQualifiedName) { - Type cached = nameToTypeCache.get(fullyQualifiedName); - if (cached != null) { - return cached; - } - return nameToTypeCache.computeIfAbsent(fullyQualifiedName, t -> { - ITypeBinding typeBinding = resolveType(t); - return typeBinding != null ? type(typeBinding) : Type.UNKNOWN; - }); - } - - @Nullable - ITypeBinding resolveType(String name) { - int dimensions = 0; - int end = name.length() - 1; - while (name.charAt(end) == ']') { - end -= 2; - dimensions++; - } - name = name.substring(0, end + 1); - - ITypeBinding typeBinding = ast.resolveWellKnownType(name); - if (typeBinding == null) { - typeBinding = ASTUtils.resolveType(ast, name); - if (typeBinding == null) { - return null; - } - } - return dimensions == 0 ? typeBinding : typeBinding.createArrayType(dimensions); - } - - IAnnotationBinding[] resolvePackageAnnotations(String packageName) { - return ASTUtils.resolvePackageAnnotations(ast, packageName); + return Type.UNKNOWN; } public Runnable getEnvironmentCleaner() { - return ASTUtils.getEnvironmentCleaner(ast); + return () -> { + }; } public Set undefinedTypes() { - return Collections.unmodifiableSet(undefinedTypes); + return Collections.emptySet(); } + } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java index dcd11673412..c3440bc5471 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java @@ -16,17 +16,10 @@ */ package org.sonar.java.model; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import javax.annotation.Nullable; -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.Modifier; -import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; @@ -35,449 +28,136 @@ abstract class JSymbol implements Symbol { - protected final JSema sema; - protected final IBinding binding; - - /** - * Cache for {@link #hashCode()}. - */ - private int hashCode; - - /** - * Cache for {@link #owner()}. - */ - private Symbol owner; - - /** - * Cache for {@link #metadata()}. - */ - private SymbolMetadata metadata; - - JSymbol(JSema sema, IBinding binding) { - this.sema = Objects.requireNonNull(sema); - this.binding = Objects.requireNonNull(binding); + enum Kind { + TYPE, + METHOD, + VARIABLE, + PACKAGE } - @Override - public final boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof JSymbol)) { - return false; - } - JSymbol other = (JSymbol) obj; - if (this.binding.getKind() != other.binding.getKind()) { - return false; - } - switch (this.binding.getKind()) { - case IBinding.TYPE: - return JType.areEqual( - (ITypeBinding) this.binding, - (ITypeBinding) other.binding - ); - case IBinding.VARIABLE: - return areEqualVariables( - this, - other - ); - case IBinding.METHOD: - return areEqualMethodSymbols((JMethodSymbol) this, (JMethodSymbol) other); - default: - return super.equals(obj); - } - } - - private static boolean areEqualVariables(JSymbol thisVariableSymbol, JSymbol otherVariableSymbol) { - IVariableBinding thisBinding = (IVariableBinding) thisVariableSymbol.binding; - IVariableBinding otherBinding = (IVariableBinding) otherVariableSymbol.binding; - return thisBinding.getVariableId() == otherBinding.getVariableId() - && thisVariableSymbol.owner().equals(otherVariableSymbol.owner()); - } - - /** - * Method symbols are considered equal if they have the same name, same owner, same parameter types, same type parameters and same type arguments. - * In this example: - *
-   * {@code   boolean foo(T t, String s);
-   *  foo(43, "bar");}
-   * 
- * The first symbol {@code foo} has type parameter {@code T}, parameter types {@code T} and {@code String}, and no type arguments. - * The symbol {@code foo} on the second line has no type parameter, parameter types are {@code Integer} and {@code String}, and has type argument {@code Integer}. - * So the two {@code foo} symbols are not equal, although they may refer to the same method. - */ - private static boolean areEqualMethodSymbols(JMethodSymbol thisMethodSymbol, JMethodSymbol otherMethodSymbol) { - IMethodBinding thisBinding = (IMethodBinding) thisMethodSymbol.binding; - IMethodBinding otherBinding = (IMethodBinding) otherMethodSymbol.binding; - // In the case of lambdas, ecj doesn't assign a name to the different symbols, unlike the Java compiler. - // We work around that by comparing keys provided by ecj. - if (thisMethodSymbol.isLambda() && otherMethodSymbol.isLambda()) { - return Objects.equals(thisBinding.getKey(), otherBinding.getKey()); - } - return thisMethodSymbol.name().equals(otherMethodSymbol.name()) - && thisMethodSymbol.owner().equals(otherMethodSymbol.owner()) - && Arrays.equals(thisBinding.getParameterTypes(), otherBinding.getParameterTypes()) - && Arrays.equals(thisBinding.getTypeParameters(), otherBinding.getTypeParameters()) - && Arrays.equals(thisBinding.getTypeArguments(), otherBinding.getTypeArguments()); - } + protected final JSema sema; + private final Kind kind; - @Override - public final int hashCode() { - if (hashCode == 0) { - Symbol resolvedOwner = owner(); - hashCode = resolvedOwner == null ? 0 : (resolvedOwner.hashCode() * 31); - hashCode += name().hashCode(); - } - return hashCode; + JSymbol(JSema sema, Kind kind) { + this.sema = Objects.requireNonNull(sema); + this.kind = kind; } - /** - * @see JType#name() - */ @Override public final String name() { - if (binding.getKind() == IBinding.METHOD && ((IMethodBinding) binding).isConstructor()) { - return ""; - } - if (binding.getKind() == IBinding.TYPE && ((ITypeBinding) binding).isParameterizedType()) { - // without names of parameters - return ((ITypeBinding) binding).getErasure().getName(); - } - return binding.getName(); + return ""; } - /** - * @see #enclosingClass() - */ @Override public final Symbol owner() { - if (isUnknown()) { - return Symbol.UNKNOWN_SYMBOL; - } - if (owner == null) { - owner = convertOwner(); - } - return owner; - } - - private Symbol convertOwner() { - switch (binding.getKind()) { - case IBinding.PACKAGE: - return Symbol.ROOT_PACKAGE; - case IBinding.TYPE: - return typeOwner((ITypeBinding) binding); - case IBinding.METHOD: - return methodOwner((IMethodBinding) binding); - case IBinding.VARIABLE: - return variableOwner((IVariableBinding) binding); - default: - throw new IllegalStateException(unexpectedBinding()); - } - } - - private String unexpectedBinding() { - return "Unexpected binding Kind: " + binding.getKind(); - } - - private Symbol typeOwner(ITypeBinding typeBinding) { - if (typeBinding.isPrimitive()) { - return Symbol.ROOT_PACKAGE; - } - IMethodBinding declaringMethod = typeBinding.getDeclaringMethod(); - if (declaringMethod != null) { - // local type - return sema.methodSymbol(declaringMethod); - } - ITypeBinding declaringClass = typeBinding.getDeclaringClass(); - if (declaringClass != null) { - // member type - return sema.typeSymbol(declaringClass); - } - // top-level type - return sema.packageSymbol(typeBinding.getPackage()); - } - - private Symbol methodOwner(IMethodBinding methodBinding) { - return sema.typeSymbol(methodBinding.getDeclaringClass()); - } - - private Symbol variableOwner(IVariableBinding variableBinding) { - if (!variableBinding.isRecordComponent()) { - IMethodBinding declaringMethod = variableBinding.getDeclaringMethod(); - if (declaringMethod != null) { - return sema.methodSymbol(declaringMethod); - } - ITypeBinding declaringClass = variableBinding.getDeclaringClass(); - if (declaringClass != null) { - // field - return sema.typeSymbol(declaringClass); - } - } - return ownerOfRecordComponentConstant(variableBinding); - } - - private Symbol ownerOfRecordComponentConstant(IVariableBinding variableBinding) { - Tree node = sema.declarations.get(variableBinding); - if (node == null) { - // array.length - return Symbol.UNKNOWN_SYMBOL; - } - boolean initializerBlock = false; - boolean staticInitializerBlock = false; - while (true) { - node = node.parent(); - switch (node.kind()) { - case CLASS, - RECORD, - ENUM: - JTypeSymbol typeSymbol = sema.typeSymbol(((ClassTreeImpl) node).typeBinding); - if (initializerBlock) { - return sema.initializerBlockSymbol(typeSymbol); - } - if (staticInitializerBlock) { - return sema.staticInitializerBlockSymbol(typeSymbol); - } - // Record parameters - return typeSymbol; - case INITIALIZER: - initializerBlock = true; - break; - case STATIC_INITIALIZER: - staticInitializerBlock = true; - break; - case METHOD, - CONSTRUCTOR: - // local variable declaration in recovered method - // and recovered methods do not have bindings - return MethodSymbol.UNKNOWN_METHOD; - default: - // continue exploring parent - break; - } - } + return kind == Kind.PACKAGE ? Symbol.ROOT_PACKAGE : Symbol.UNKNOWN_SYMBOL; } @Override public final Type type() { - switch (binding.getKind()) { - case IBinding.TYPE: - return sema.type((ITypeBinding) binding); - case IBinding.VARIABLE: - ITypeBinding variableType = ((IVariableBinding) binding).getType(); - return variableType != null ? sema.type(variableType) : Type.UNKNOWN; - case IBinding.PACKAGE, - IBinding.METHOD: - return Type.UNKNOWN; - default: - throw new IllegalStateException(unexpectedBinding()); - } + return Type.UNKNOWN; } @Override public final boolean isVariableSymbol() { - return binding.getKind() == IBinding.VARIABLE; + return kind == Kind.VARIABLE; } @Override public final boolean isTypeSymbol() { - return !isUnknown() && binding.getKind() == IBinding.TYPE; + return kind == Kind.TYPE; } @Override public final boolean isMethodSymbol() { - return binding.getKind() == IBinding.METHOD; + return kind == Kind.METHOD; } @Override public final boolean isPackageSymbol() { - return binding.getKind() == IBinding.PACKAGE; + return kind == Kind.PACKAGE; } @Override public final boolean isStatic() { - return Modifier.isStatic(binding.getModifiers()); + return false; } @Override public final boolean isFinal() { - return Modifier.isFinal(binding.getModifiers()); + return false; } @Override public final boolean isEnum() { - switch (binding.getKind()) { - case IBinding.TYPE: - return ((ITypeBinding) binding).isEnum(); - case IBinding.VARIABLE: - return ((IVariableBinding) binding).isEnumConstant(); - default: - return false; - } + return false; } @Override public final boolean isInterface() { - return binding.getKind() == IBinding.TYPE - && ((ITypeBinding) binding).isInterface(); + return false; } @Override public final boolean isAbstract() { - return Modifier.isAbstract(binding.getModifiers()); + return false; } @Override public final boolean isPublic() { - return Modifier.isPublic(binding.getModifiers()); + return false; } @Override public final boolean isPrivate() { - return Modifier.isPrivate(binding.getModifiers()); + return false; } @Override public final boolean isProtected() { - return Modifier.isProtected(binding.getModifiers()); + return false; } @Override public final boolean isPackageVisibility() { - return !isPublic() && !isPrivate() && !isProtected(); + return false; } @Override public final boolean isDeprecated() { - return binding.isDeprecated(); + return false; } @Override public final boolean isVolatile() { - return Modifier.isVolatile(binding.getModifiers()); + return false; } @Override public final boolean isUnknown() { - return binding.isRecovered(); + return true; } @Override public final SymbolMetadata metadata() { - if (metadata == null) { - try { - metadata = convertMetadata(); - } catch (RuntimeException e) { - // ECJ raises exception in rare occasions, when it is the case, we don't want to prevent the whole analysis of the file - // ECJ 3.41.0: no longer throws the exception on the code that was used in the test - metadata = Symbols.EMPTY_METADATA; - } - } - return metadata; - } - - private SymbolMetadata convertMetadata() { - switch (binding.getKind()) { - case IBinding.PACKAGE: - return new JSymbolMetadata(sema, this, sema.resolvePackageAnnotations(binding.getName())); - case IBinding.VARIABLE: - ITypeBinding type = ((IVariableBinding) binding).getType(); - if (type == null) { - return new JSymbolMetadata(sema, this, binding.getAnnotations()); - } - return convertMetadata(type); - case IBinding.METHOD: - ITypeBinding returnType = ((IMethodBinding) binding).getReturnType(); - // In rare circumstances, when the semantic information is incomplete, returnType can be null. - if (returnType == null) { - return Symbols.EMPTY_METADATA; - } - return convertMetadata(returnType); - default: - return new JSymbolMetadata(sema, this, binding.getAnnotations()); - } - } - - private SymbolMetadata convertMetadata(ITypeBinding type) { - return JSymbolMetadata.of(sema, this, type, binding.getAnnotations()); + return Symbols.EMPTY_METADATA; } - /** - * @see #owner() - */ @Nullable @Override public final TypeSymbol enclosingClass() { - switch (binding.getKind()) { - case IBinding.PACKAGE: - return null; - case IBinding.TYPE: - return typeEnclosingClass((ITypeBinding) binding); - case IBinding.METHOD: - return methodEnclosingClass((IMethodBinding) binding); - case IBinding.VARIABLE: - return variableEnclosingClass((IVariableBinding) binding); - default: - throw new IllegalStateException(unexpectedBinding()); - } - } - - private TypeSymbol typeEnclosingClass(ITypeBinding typeBinding) { - ITypeBinding declaringClass = typeBinding.getDeclaringClass(); - if (declaringClass != null) { - // nested (member or local) type - return sema.typeSymbol(declaringClass); - } - // top-level type - return (TypeSymbol) this; - } - - private TypeSymbol methodEnclosingClass(IMethodBinding methodBinding) { - ITypeBinding declaringClass = methodBinding.getDeclaringClass(); - return sema.typeSymbol(declaringClass); - } - - private TypeSymbol variableEnclosingClass(IVariableBinding variableBinding) { - ITypeBinding declaringClass = variableBinding.getDeclaringClass(); - if (declaringClass != null) { - // field - return sema.typeSymbol(declaringClass); - } - IMethodBinding declaringMethod = variableBinding.getDeclaringMethod(); - if (declaringMethod != null) { - // local variable - return sema.typeSymbol(declaringMethod.getDeclaringClass()); - } - Tree node = sema.declarations.get(variableBinding); - if (node == null) { - // array.length - return TypeSymbol.UNKNOWN_TYPE; - } - do { - node = node.parent(); - switch (node.kind()) { - case CLASS, - RECORD, - ENUM: - // variable declaration in a static or instance initializer - // or local variable declaration in recovered method - return sema.typeSymbol(((ClassTreeImpl) node).typeBinding); - default: - // continue exploring parent - break; - } - } while (true); + return null; } @Override public final List usages() { - List usages = sema.usages.get(JSema.declarationBinding(binding)); - return usages != null ? usages : Collections.emptyList(); + return Collections.emptyList(); } @Nullable @Override public Tree declaration() { - return sema.declarations.get(JSema.declarationBinding(binding)); + return null; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java index 2b9c5b6b862..900cd1f7873 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java @@ -17,27 +17,17 @@ package org.sonar.java.model; import java.util.Arrays; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; -import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.IAnnotationBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; -import org.sonar.plugins.java.api.semantic.Symbol; -import org.sonar.plugins.java.api.semantic.SymbolMetadata; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationInstance; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityData; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityLevel; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityType; import org.sonar.plugins.java.api.tree.AnnotationTree; -import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.Tree; -import static org.sonar.java.model.JSymbolMetadataNullabilityHelper.getNullabilityDataAtLevel; - -final class JSymbolMetadata implements SymbolMetadata { +final class JSymbolMetadata { private static final NullabilityData[] NO_ANNOTATION_NULLABILITY = forEachLevel(level -> new JNullabilityData(NullabilityType.NO_ANNOTATION, level, null, null, false)); @@ -45,77 +35,7 @@ final class JSymbolMetadata implements SymbolMetadata { private static final NullabilityData[] UNKNOWN_NULLABILITY = forEachLevel(level -> new JNullabilityData(NullabilityType.UNKNOWN, level, null, null, false)); - private final JSema sema; - private final Symbol symbol; - private final IAnnotationBinding[] symbolBindings; - private final SymbolMetadata[] parameterMetas; - private final IAnnotationBinding[] annotationBindings; - - /** - * Cache for {@link #annotations()}. - */ - private List annotations; - - /** - * Cache for {@link #symbolAnnotations()}. - */ - private List symbolAnnotations; - - private final Map nullabilityCache = new EnumMap<>(NullabilityTarget.class); - - private final boolean isTypeArgMetadata; - - JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] annotationBindings) { - this.sema = Objects.requireNonNull(sema); - this.symbol = symbol; - this.symbolBindings = annotationBindings; - this.parameterMetas = new SymbolMetadata[0]; - this.isTypeArgMetadata = false; - - this.annotationBindings = annotationBindings; - } - - private JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] symbolAnnotations, JSymbolMetadata[] parameterMetaDatas, boolean isTypeArgMetadata) { - this.sema = Objects.requireNonNull(sema); - this.symbol = symbol; - this.symbolBindings = symbolAnnotations; - this.parameterMetas = parameterMetaDatas; - this.isTypeArgMetadata = isTypeArgMetadata; - - int total = symbolBindings.length; - for (JSymbolMetadata parameterMetaData : parameterMetaDatas) { - total += parameterMetaData.annotationBindings.length; - } - - this.annotationBindings = new IAnnotationBinding[total]; - int current = symbolAnnotations.length; - System.arraycopy(symbolAnnotations, 0, this.annotationBindings, 0, symbolAnnotations.length); - - for (JSymbolMetadata parameterMetaData : parameterMetaDatas) { - var paramBindings = parameterMetaData.annotationBindings; - System.arraycopy(paramBindings, 0, this.annotationBindings, current, paramBindings.length); - current += paramBindings.length; - } - } - - public static JSymbolMetadata of(JSema sema, Symbol owner, ITypeBinding type, IAnnotationBinding[] externalAnnotations) { - var symbolAnnotations = new IAnnotationBinding[externalAnnotations.length + type.getTypeAnnotations().length]; - System.arraycopy(externalAnnotations, 0, symbolAnnotations, 0, externalAnnotations.length); - System.arraycopy(type.getTypeAnnotations(), 0, symbolAnnotations, externalAnnotations.length, type.getTypeAnnotations().length); - - var parameterMetaDatas = Arrays.stream(type.getTypeArguments()) - .map(param -> ofTypeArg(sema, owner, param)) - .toArray(JSymbolMetadata[]::new); - - return new JSymbolMetadata(sema, owner, symbolAnnotations, parameterMetaDatas, false); - } - - private static JSymbolMetadata ofTypeArg(JSema sema, Symbol owner, ITypeBinding param) { - var symbolAnnotations = param.getTypeAnnotations(); - var parameterMetaDatas = Arrays.stream(param.getTypeArguments()) - .map(p -> ofTypeArg(sema, owner, p)) - .toArray(JSymbolMetadata[]::new); - return new JSymbolMetadata(sema, owner, symbolAnnotations, parameterMetaDatas, true); + private JSymbolMetadata() { } private static NullabilityData[] forEachLevel(Function initializer) { @@ -130,178 +50,6 @@ public static NullabilityData unknownNullabilityAt(NullabilityLevel level) { return UNKNOWN_NULLABILITY[level.ordinal()]; } - @Override - public List annotations() { - if (annotations == null) { - annotations = Arrays.stream(annotationBindings) - .map(sema::annotation) - .collect(Collectors.toList()); - } - return annotations; - } - - @Override - public List symbolAnnotations() { - if (symbolAnnotations == null) { - symbolAnnotations = Arrays.stream(symbolBindings) - .map(sema::annotation) - .collect(Collectors.toList()); - } - return symbolAnnotations; - } - - @Override - public final boolean isAnnotatedWith(String fullyQualifiedName) { - for (AnnotationInstance a : annotations()) { - if (a.symbol().type().is(fullyQualifiedName)) { - return true; - } - } - return false; - } - - @Nullable - @Override - public List valuesForAnnotation(String fullyQualifiedNameOfAnnotation) { - for (AnnotationInstance a : annotations()) { - if (a.symbol().type().is(fullyQualifiedNameOfAnnotation)) { - // TODO what about repeating annotations? - return a.values(); - } - } - return null; - } - - @Override - public NullabilityData nullabilityData() { - NullabilityTarget target = getTarget(symbol); - if (target == null) { - return unknownNullabilityAt(NullabilityLevel.UNKNOWN); - } - return nullabilityData(target); - } - - @Override - public NullabilityData nullabilityData(NullabilityTarget target) { - return nullabilityCache.computeIfAbsent(target, this::resolveNullability); - } - - @Nullable - @Override - public AnnotationTree findAnnotationTree(AnnotationInstance annotationInstance) { - Tree declaration = symbol.declaration(); - if (declaration != null) { - return findAnnotationTree(ModifiersUtils.getAnnotations(declaration), annotationInstance); - } - return null; - } - - @Override - public SymbolMetadata[] parametersMetadata() { - return parameterMetas; - } - - @Nullable - private static AnnotationTree findAnnotationTree(List annotations, AnnotationInstance annotationInstance) { - for (AnnotationTree annotationTree : annotations) { - if (annotationTree.symbolType().symbol() == annotationInstance.symbol() && - annotationTree.arguments().size() == annotationInstance.values().size()) { - return annotationTree; - } - } - return null; - } - - private NullabilityData resolveNullability(NullabilityTarget target) { - NullabilityLevel currentLevel = getLevel(symbol); - NullabilityData nullabilityDataAtLevel = getNullabilityDataAtLevel(this, target, currentLevel); - if (nullabilityDataAtLevel.type() != NullabilityType.NO_ANNOTATION) { - return nullabilityDataAtLevel; - } - - // Check nullability from the inheritance hierarchy. - // Skipped for type argument metadata: type arg nullability must not inherit from the enclosing - // method's return type annotations, which belong to the outer type, not the type argument. - if (symbol.isMethodSymbol() && !isTypeArgMetadata) { - NullabilityData nullabilityDataFromInheritance = getNullabilityDataFromInheritance((Symbol.MethodSymbol) symbol, target); - if (nullabilityDataFromInheritance.type() != NullabilityType.NO_ANNOTATION) { - return nullabilityDataFromInheritance; - } - } - - // Not annotated or meta annotated, check upper level in the ownership hierarchy... - if (symbol.isPackageSymbol()) { - return NO_ANNOTATION_NULLABILITY[currentLevel.ordinal()]; - } - Symbol owner = getEffectiveOwner(symbol, currentLevel); - if (owner == null) return unknownNullabilityAt(currentLevel); - var metadata = owner.metadata(); - return metadata.nullabilityData(target); - } - - private static NullabilityData getNullabilityDataFromInheritance(Symbol.MethodSymbol methodSymbol, NullabilityTarget target) { - List overriddenSymbols = methodSymbol.overriddenSymbols(); - NullabilityLevel level = NullabilityLevel.METHOD; - for (Symbol.MethodSymbol overriddenSymbol : overriddenSymbols) { - SymbolMetadata metadata = overriddenSymbol.metadata(); - NullabilityData nullabilityData = getNullabilityDataAtLevel(metadata, target, level); - if (nullabilityData.type() != NullabilityType.NO_ANNOTATION && !nullabilityData.equals(unknownNullabilityAt(level))) { - return nullabilityData; - } - } - return NO_ANNOTATION_NULLABILITY[level.ordinal()]; - } - - @CheckForNull - private static Symbol getEffectiveOwner(Symbol symbol, NullabilityLevel currentLevel) { - Symbol owner = symbol.owner(); - if (owner != null && owner.isMethodSymbol() && currentLevel == NullabilityLevel.CLASS) { - // Owner of anonymous class can be a method symbol, but the nullability of the method is unrelated to the ones from the anonymous classes. - return getEffectiveOwner(owner, currentLevel); - } - return owner; - } - - private static NullabilityLevel getLevel(Symbol symbol) { - if (symbol.isVariableSymbol()) { - return NullabilityLevel.VARIABLE; - } else if (symbol.isMethodSymbol()) { - return NullabilityLevel.METHOD; - } else if (symbol.isTypeSymbol()) { - return NullabilityLevel.CLASS; - } else if (symbol.isPackageSymbol()) { - return NullabilityLevel.PACKAGE; - } - return NullabilityLevel.UNKNOWN; - } - - @CheckForNull - private static NullabilityTarget getTarget(Symbol symbol) { - if (symbol.isMethodSymbol()) { - return NullabilityTarget.METHOD; - } else if (symbol.isVariableSymbol()) { - Symbol owner = symbol.owner(); - if (owner != null && owner.isTypeSymbol()) { - return NullabilityTarget.FIELD; - } - Tree variableTree = symbol.declaration(); - if (variableTree == null) { - return NullabilityTarget.PARAMETER; - } - Tree variableTreeParent = variableTree.parent(); - if (variableTreeParent == null || variableTreeParent instanceof MethodTree) { - return NullabilityTarget.PARAMETER; - } - // If variableTreeParent is not a lambda, we consider that the target is a local variable. - if (!variableTreeParent.is(Tree.Kind.LAMBDA_EXPRESSION)) { - return NullabilityTarget.LOCAL_VARIABLE; - } - // When variableTreeParent is an instance of LambdaExpressionTree we intentionally return null - // because parameters of lambdas are not supported. - } - return null; - } - static final class JNullabilityData implements NullabilityData { private final NullabilityType type; @@ -378,50 +126,4 @@ public Tree declaration() { } } - static final class JAnnotationInstance implements AnnotationInstance { - private final JSema sema; - private final IAnnotationBinding annotationBinding; - - /** - * Cache for {@link #values()}. - */ - private List values; - - JAnnotationInstance(JSema sema, IAnnotationBinding annotationBinding) { - this.sema = sema; - this.annotationBinding = annotationBinding; - } - - @Override - public Symbol symbol() { - return sema.typeSymbol(annotationBinding.getAnnotationType()); - } - - @Override - public List values() { - if (values == null) { - values = Arrays.stream(annotationBinding.getDeclaredMemberValuePairs()) - .map(p -> new AnnotationValueImpl(p.getName(), convertAnnotationValue(p.getValue()))) - .collect(Collectors.toList()); - } - return values; - } - - private Object convertAnnotationValue(Object value) { - return switch (value) { - case IVariableBinding iVariableBinding -> sema.variableSymbol(iVariableBinding); - case ITypeBinding iTypeBinding -> sema.typeSymbol(iTypeBinding); - case IAnnotationBinding iAnnotationBinding -> sema.annotation(iAnnotationBinding); - case Object[] a -> { - Object[] result = new Object[a.length]; - for (int i = 0; i < a.length; i++) { - result[i] = convertAnnotationValue(a[i]); - } - yield result; - } - case null, default -> value; - }; - } - } - } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JType.java b/java-frontend/src/main/java/org/sonar/java/model/JType.java index ee13c72eebe..d132787c39c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JType.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JType.java @@ -16,351 +16,146 @@ */ package org.sonar.java.model; -import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.TreeSet; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; final class JType implements Type, Type.ArrayType { - private static final Logger LOG = LoggerFactory.getLogger(JType.class); - - final JSema sema; - final ITypeBinding typeBinding; - - private final String fullyQualifiedName; - - /** - * Cache for {@link #primitiveWrapperType()}. - */ - private Type primitiveWrapperType; - - /** - * Cache for {@link #primitiveType()}. - */ - private Type primitiveType; - - /** - * Cache for {@link #declaringType()}. - */ - private Type declaringType; - - /** - * Cache for {@link #typeArguments()}. - */ - private List typeArguments; - - JType(JSema sema, ITypeBinding typeBinding) { - this.sema = Objects.requireNonNull(sema); - this.typeBinding = Objects.requireNonNull(typeBinding); - this.fullyQualifiedName = fullyQualifiedName(typeBinding); - } + private static final String UNKNOWN_NAME = "!Unknown!"; @Override public boolean is(String fullyQualifiedName) { - return fullyQualifiedName.equals(this.fullyQualifiedName()); + return false; } @Override public boolean isSubtypeOf(String fullyQualifiedName) { - return isSubtypeOf(sema.getClassType(fullyQualifiedName)); + return false; } @Override public boolean isSubtypeOf(Type superType) { - return !superType.isUnknown() - && isSubtype(this.typeBinding, ((JType) superType).typeBinding); - } - - private static boolean isSubtype(ITypeBinding left, ITypeBinding right) { - if (left.isRecovered()) { - return false; - } - if (left.isNullType()) { - return !right.isPrimitive(); - } - try { - return left.isSubTypeCompatible(right); - } catch (NullPointerException ex) { - // In rare circumstances, ECJ may produce a NPE while calling isSubTypeCompatible(), see SONARJAVA-4390 - LOG.debug("NullPointerException while resolving isSubTypeCompatible()", ex); - return false; - } + return false; } @Override public boolean isArray() { - return typeBinding.isArray(); + return false; } @Override public boolean isClass() { - return typeBinding.isClass() - || typeBinding.isInterface() - || typeBinding.isEnum(); + return false; } @Override public boolean isVoid() { - return "void".equals(fullyQualifiedName()); + return false; } @Override public boolean isPrimitive() { - return typeBinding.isPrimitive() - && !isVoid(); + return false; } @Override public boolean isPrimitive(Primitives primitive) { - // TODO suboptimal - return primitive.name().toLowerCase(Locale.ROOT).equals(fullyQualifiedName()); + return false; } @Override public boolean isPrimitiveWrapper() { - return isClass() && JUtils.WRAPPER_TO_PRIMITIVE.containsKey(fullyQualifiedName()); + return false; } @Nullable @Override public Type primitiveWrapperType() { - if (primitiveWrapperType == null) { - String name = JUtils.PRIMITIVE_TO_WRAPPER.get(fullyQualifiedName()); - if (name == null) { - return null; - } - ITypeBinding resolvedType = sema.resolveType(name); - if (resolvedType == null) { - return null; - } - primitiveWrapperType = sema.type(resolvedType); - } - return primitiveWrapperType; + return null; } @Nullable @Override public Type primitiveType() { - if (primitiveType == null) { - String name = JUtils.WRAPPER_TO_PRIMITIVE.get(fullyQualifiedName()); - if (name == null) { - return null; - } - ITypeBinding resolvedType = sema.resolveType(name); - if (resolvedType == null) { - return null; - } - primitiveType = sema.type(resolvedType); - } - return primitiveType; + return null; } @Override public boolean isNullType() { - return !isUnknown() && typeBinding.isNullType(); + return false; } @Override public boolean isTypeVar() { - return !isUnknown() && typeBinding.isTypeVariable(); + return false; } @Override public boolean isRawType() { - if (isUnknown()) { - return false; - } - return typeBinding.isRawType(); + return false; } @Override public Type declaringType() { - if (declaringType == null) { - if (isUnknown()) { - return this; - } - declaringType = sema.type(typeBinding.getTypeDeclaration()); - } - return declaringType; + return this; } @Override public boolean isUnknown() { - return typeBinding.isRecovered(); + return true; } @Override public boolean isNumerical() { - switch (fullyQualifiedName()) { - case "byte", - "char", - "short", - "int", - "long", - "float", - "double": - return true; - default: - return false; - } + return false; } @Override public String fullyQualifiedName() { - return fullyQualifiedName; - } - - private static String fullyQualifiedName(ITypeBinding typeBinding) { - String qualifiedName; - if (typeBinding.isNullType()) { - qualifiedName = ""; - } else if (typeBinding.isPrimitive()) { - qualifiedName = typeBinding.getName(); - } else if (typeBinding.isArray()) { - qualifiedName = fullyQualifiedName(typeBinding.getComponentType()) + "[]"; - } else if (typeBinding.isCapture()) { - qualifiedName = "!capture!"; - } else if (typeBinding.isTypeVariable()) { - qualifiedName = typeBinding.getName(); - } else { - qualifiedName = typeBinding.getBinaryName(); - if (qualifiedName == null) { - // e.g. anonymous class in unreachable code - qualifiedName = typeBinding.getKey(); - } - } - if (typeBinding.isIntersectionType()) { - TreeSet intersectionTypes = new TreeSet<>(); - intersectionTypes.add(qualifiedName); - for (ITypeBinding typeBound : typeBinding.getTypeBounds()) { - intersectionTypes.add(fullyQualifiedName(typeBound)); - } - qualifiedName = String.join(" & ", intersectionTypes); - } - return qualifiedName; + return UNKNOWN_NAME; } - @Override - public boolean isIntersectionType() { - return typeBinding.isIntersectionType(); - } - - @Override - public Type[] getIntersectionTypes() { - if (!isIntersectionType()) { - return new Type[] { this }; - } - ITypeBinding[] bounds = typeBinding.getTypeBounds(); - Type[] types = new Type[bounds.length]; - for (int i = 0; i < bounds.length; i++) { - types[i] = sema.type(bounds[i]); - } - return types; - } - - /** - * @see JSymbol#name() - */ @Override public String name() { - if (typeBinding.isNullType()) { - return ""; - } else if (typeBinding.isParameterizedType()) { - // without names of parameters - return typeBinding.getErasure().getName(); - } - return typeBinding.getName(); - } - - @Override - public String toString() { - return name(); + return UNKNOWN_NAME; } @Override public Symbol.TypeSymbol symbol() { - return sema.typeSymbol(typeBinding); + return Symbol.TypeSymbol.UNKNOWN_TYPE; } @Override public Type erasure() { - return sema.type(typeBinding.getErasure()); - } - - @Override - public Type elementType() { - return sema.type(typeBinding.getComponentType()); + return this; } @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj instanceof JType other) { - return areEqual(this.typeBinding, other.typeBinding); - } + public boolean isParameterized() { return false; } @Override - public int hashCode() { - return fullyQualifiedName().hashCode(); - } - - static boolean areEqual(@Nullable ITypeBinding binding1, @Nullable ITypeBinding binding2) { - if (binding1 == null || binding2 == null) { - return binding1 == binding2; - } - if (binding1.isWildcardType()) { - return binding2.isWildcardType() - && binding1.isUpperbound() == binding2.isUpperbound() - && areEqual(binding1.getBound(), binding2.getBound()); - } - // TODO compare declaring class, method, member - return binding1.getTypeDeclaration().equals(binding2.getTypeDeclaration()) - && isParameterizedOrGeneric(binding1) == isParameterizedOrGeneric(binding2) - && binding1.isRawType() == binding2.isRawType() - && Arrays.equals(binding1.getTypeParameters(), binding2.getTypeParameters()) - && Arrays.equals(binding1.getTypeArguments(), binding2.getTypeArguments()); - } - - static ITypeBinding normalize(ITypeBinding typeBinding) { - ITypeBinding typeDeclaration = typeBinding.getTypeDeclaration(); - if (typeBinding.isLocal()) { - return typeDeclaration; - } - return typeBinding.isParameterizedType() && Arrays.equals(typeDeclaration.getTypeParameters(), typeBinding.getTypeArguments()) - ? typeDeclaration - : typeBinding; + public List typeArguments() { + return Collections.emptyList(); } - private static boolean isParameterizedOrGeneric(ITypeBinding typeBinding) { - return typeBinding.isParameterizedType() || typeBinding.isGenericType(); + @Override + public Type elementType() { + return Type.UNKNOWN; } @Override - public boolean isParameterized() { - return typeBinding.isParameterizedType() - // when diamond operator is not fully resolved by ECJ, there is 0 typeArguments, while ECJ - // knows it is a Parameterized Type - && !typeArguments().isEmpty(); + public boolean isIntersectionType() { + return false; } @Override - public List typeArguments() { - if (typeArguments == null) { - typeArguments = sema.types(typeBinding.getTypeArguments()); - } - return typeArguments; + public Type[] getIntersectionTypes() { + return new Type[0]; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java index 0d41214e309..0d6db6d3788 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java @@ -16,230 +16,63 @@ */ package org.sonar.java.model; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ClassTree; -import org.sonar.plugins.java.api.tree.IdentifierTree; -import org.sonar.plugins.java.api.tree.VariableTree; final class JTypeSymbol extends JSymbol implements Symbol.TypeSymbol { - /** - * Cache for {@link #superClass()}. - */ - private Type superclass = Type.UNKNOWN; - - /** - * Cache for {@link #interfaces()}. - */ - private List interfaces; - - /** - * Cache for {@link #memberSymbols()}. - */ - private Collection memberSymbols; - - /** - * Cache for {@link #superTypes()}. - */ - private Set superTypes; - - final SpecialField superSymbol = new SpecialField() { - @Override - public String name() { - return "super"; - } - - @Override - public Type type() { - if (typeBinding().isInterface()) { - // JLS § 15.12.1: - // for "T.super.foo()", if T is an interface, 'super' keyword is used to access method of the interface itself - return JTypeSymbol.this.type(); - } - Type superClass = JTypeSymbol.this.superClass(); - return superClass == null ? Type.UNKNOWN : superClass; - } - }; - - final SpecialField thisSymbol = new SpecialField() { - @Override - public String name() { - return "this"; - } - - @Override - public Type type() { - return JTypeSymbol.this.type(); - } - }; - - JTypeSymbol(JSema sema, ITypeBinding typeBinding) { - super(sema, typeBinding); - } - - ITypeBinding typeBinding() { - return (ITypeBinding) binding; + JTypeSymbol(JSema sema) { + super(sema, Kind.TYPE); } @CheckForNull @Override public Type superClass() { - if (superclass == Type.UNKNOWN) { - superclass = convertSuperClass(); - } - return superclass; - } - - @CheckForNull - private Type convertSuperClass() { - if (typeBinding().isInterface() || typeBinding().isArray()) { - ITypeBinding objectBinding = sema.resolveType("java.lang.Object"); - return objectBinding != null ? sema.type(objectBinding) : Type.UNKNOWN; - } else if (typeBinding().getSuperclass() == null) { - // java.lang.Object - return null; - } else { - return sema.type(typeBinding().getSuperclass()); - } + return null; } @Override public List interfaces() { - if (interfaces == null) { - interfaces = sema.types(typeBinding().getInterfaces()); - } - return interfaces; + return Collections.emptyList(); } @Override public Collection memberSymbols() { - if (memberSymbols == null) { - memberSymbols = convertMemberSymbols(); - } - return memberSymbols; - } - - private Collection convertMemberSymbols() { - return Stream.concat( - Stream.concat( - Arrays.stream(typeBinding().getDeclaredTypes()).map(sema::typeSymbol), - Arrays.stream(typeBinding().getDeclaredFields()).map(sema::variableSymbol)), - Arrays.stream(typeBinding().getDeclaredMethods()).map(sema::methodSymbol)) - .collect(Collectors.toCollection(ArrayList::new)); + return Collections.emptyList(); } @Override public Collection lookupSymbols(String name) { - return memberSymbols().stream() - .filter(m -> name.equals(m.name())) - .collect(Collectors.toSet()); + return Collections.emptyList(); } @Nullable @Override public ClassTree declaration() { - return (ClassTree) super.declaration(); + return null; } @Override public Set superTypes() { - if (superTypes == null) { - if (isUnknown()) { - superTypes = Collections.emptySet(); - } else { - superTypes = new HashSet<>(); - JUtils.collectSuperTypes(superTypes, sema, typeBinding()); - } - } - return superTypes; + return Collections.emptySet(); } - @Override @Nullable + @Override public TypeSymbol outermostClass() { - Symbol symbol = this; - Symbol result = null; - while (symbol != null && !symbol.isPackageSymbol()) { - result = symbol; - symbol = symbol.owner(); - } - return (Symbol.TypeSymbol) result; + return this; } @Override public boolean isAnnotation() { - return !isUnknown() && typeBinding().isAnnotation(); - } - - abstract class SpecialField extends Symbols.DefaultSymbol implements Symbol.VariableSymbol { - @Override - public final Symbol owner() { - return JTypeSymbol.this; - } - - @Override - public final boolean isVariableSymbol() { - return true; - } - - @Override - public final boolean isFinal() { - return true; - } - - @Override - public final boolean isUnknown() { - return false; - } - - @Override - public final TypeSymbol enclosingClass() { - return JTypeSymbol.this; - } - - @Override - public final List usages() { - return Collections.emptyList(); - } - - @Nullable - @Override - public final VariableTree declaration() { - return null; - } - - @Override - public final boolean isEffectivelyFinal(){ - return false; - } - @Override - public final Optional constantValue() { - return Optional.empty(); - } - - @Override - public final boolean isLocalVariable() { - return false; - } - - @Override - public final boolean isParameter() { - return false; - } + return false; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JUtils.java b/java-frontend/src/main/java/org/sonar/java/model/JUtils.java index 6018e221558..1767701abe2 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JUtils.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JUtils.java @@ -16,7 +16,6 @@ */ package org.sonar.java.model; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; @@ -26,7 +25,6 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; @@ -67,19 +65,7 @@ public static Type wrapTypeIfPrimitive(Type type) { } public static boolean isIntersectionType(Type type) { - return !type.isUnknown() && ((JType) type).typeBinding.isIntersectionType(); - } - - public static void collectSuperTypes(Set result, JSema sema, ITypeBinding typeBinding) { - ITypeBinding s = typeBinding.getSuperclass(); - if (s != null) { - result.add(sema.type(s)); - collectSuperTypes(result, sema, s); - } - Arrays.stream(typeBinding.getInterfaces()).forEach(i -> { - result.add(sema.type(i)); - collectSuperTypes(result, sema, i); - }); + return type.isIntersectionType(); } public static Symbol getPackage(Symbol symbol) { @@ -91,10 +77,7 @@ public static Symbol getPackage(Symbol symbol) { @Nullable public static Object defaultValue(Symbol.MethodSymbol method) { - if (method.isUnknown()) { - return null; - } - return ((JMethodSymbol) method).methodBinding().getDefaultValue(); + return null; } /** @@ -162,13 +145,13 @@ public static Set directSuperTypes(Type type) { return Collections.emptySet(); } Set result = new HashSet<>(); - JType t = (JType) type; - ITypeBinding superclass = t.typeBinding.getSuperclass(); - if (superclass != null) { - result.add(t.sema.type(superclass)); + Symbol.TypeSymbol symbol = type.symbol(); + Type superClass = symbol.superClass(); + if (superClass != null && !superClass.isUnknown()) { + result.add(superClass); } - Arrays.stream(t.typeBinding.getInterfaces()) - .map(t.sema::type) + symbol.interfaces().stream() + .filter(iface -> !iface.isUnknown()) .forEach(result::add); return result; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java index ae6458f39de..1d27c113a3c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java @@ -20,9 +20,6 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; @@ -31,69 +28,41 @@ final class JVariableSymbol extends JSymbol implements Symbol.VariableSymbol { - // cache for this.constantValue() - private boolean constantValueComputed = false; - private Optional constantValue; - - JVariableSymbol(JSema sema, IVariableBinding variableBinding) { - super(sema, variableBinding); + JVariableSymbol(JSema sema) { + super(sema, Kind.VARIABLE); } @Nullable @Override public VariableTree declaration() { - return (VariableTree) super.declaration(); + return null; } @Override public boolean isEffectivelyFinal() { - return ((IVariableBinding)binding).isEffectivelyFinal(); + return false; } @Override public Optional constantValue() { - if (!constantValueComputed) { - constantValueComputed = true; - if (!isFinal() || !isStatic()) { - constantValue = Optional.empty(); - } else { - Object c = ((IVariableBinding) binding).getConstantValue(); - if (c instanceof Short shortValue) { - c = Integer.valueOf(shortValue); - } else if (c instanceof Byte byteValue) { - c = Integer.valueOf(byteValue); - } else if (c instanceof Character characterValue) { - c = Integer.valueOf(characterValue); - } - constantValue = Optional.ofNullable(c); - } - } - return constantValue; + return Optional.empty(); } @Override public boolean isLocalVariable() { - Symbol owner = owner(); - return owner != null && owner.isMethodSymbol(); + return false; } @Override public boolean isParameter() { - return ((IVariableBinding) binding).isParameter(); + return false; } - static class ParameterPlaceholderSymbol extends Symbols.DefaultSymbol implements Symbol.VariableSymbol { private final String name; - private final Symbol owner; - private final Type type; - private final SymbolMetadata metadata; - ParameterPlaceholderSymbol(int index, JSema sema, IMethodBinding owner, ITypeBinding typeBinding) { + ParameterPlaceholderSymbol(int index) { this.name = "arg" + index; - this.owner = sema.methodSymbol(owner); - this.type = sema.type(typeBinding); - this.metadata = JSymbolMetadata.of(sema, this, typeBinding, owner.getParameterAnnotations(index)); } @Override @@ -103,22 +72,22 @@ public String name() { @Override public Symbol owner() { - return owner; + return Symbol.UNKNOWN_SYMBOL; } @Override public Type type() { - return type; + return Type.UNKNOWN; } @Override public boolean isUnknown() { - return false; + return true; } @Override public TypeSymbol enclosingClass() { - return owner.enclosingClass(); + return TypeSymbol.UNKNOWN_TYPE; } @Override @@ -126,6 +95,7 @@ public List usages() { return Collections.emptyList(); } + @Nullable @Override public VariableTree declaration() { return null; @@ -153,7 +123,7 @@ public boolean isParameter() { @Override public SymbolMetadata metadata() { - return metadata; + return Symbols.EMPTY_METADATA; } @Override @@ -163,9 +133,8 @@ public boolean isVariableSymbol() { @Override public boolean isFinal() { - // From a caller perspective, it is not useful (and even not possible) to know if the parameter is final. return false; } - } + } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JWarning.java b/java-frontend/src/main/java/org/sonar/java/model/JWarning.java index 8a95e692df6..fc36ae2af61 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JWarning.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JWarning.java @@ -28,9 +28,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.CheckForNull; -import org.eclipse.jdt.core.compiler.IProblem; -import org.eclipse.jdt.core.dom.CompilationUnit; import org.sonar.java.annotations.VisibleForTesting; import org.sonar.java.ast.visitors.SubscriptionVisitor; import org.sonar.java.model.location.InternalPosition; @@ -97,30 +94,7 @@ public static class Mapper extends SubscriptionVisitor { private final PriorityQueue warnings = new PriorityQueue<>(Comparator.comparing(JWarning::start).thenComparing(JWarning::end)); - private Mapper(CompilationUnit ast, LineColumnConverter lineColumnConverter) { - Stream.of(ast.getProblems()) - .map(problem -> convert(problem, ast, lineColumnConverter)) - .filter(Objects::nonNull) - .forEach(warnings::add); - } - - @CheckForNull - private static JWarning convert(IProblem problem, CompilationUnit root, LineColumnConverter lineColumnConverter) { - for (Type type : Type.values()) { - if (type.matches(problem)) { - LineColumnConverter.Pos start = lineColumnConverter.toPos(problem.getSourceStart()); - LineColumnConverter.Pos end = lineColumnConverter.toPos(problem.getSourceStart()); - return new JWarning(problem.getMessage(), - type, - start.line(), start.columnOffset(), - end.line(), end.columnOffset()); - } - } - return null; - } - - public static Mapper warningsFor(CompilationUnit ast, LineColumnConverter lineColumnConverter) { - return new Mapper(ast, lineColumnConverter); + private Mapper() { } public void mappedInto(JavaTree.CompilationUnitTreeImpl cut) { diff --git a/java-frontend/src/main/java/org/sonar/java/model/JavaTree.java b/java-frontend/src/main/java/org/sonar/java/model/JavaTree.java index bf41aee95c2..d20af36bad2 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JavaTree.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JavaTree.java @@ -27,10 +27,6 @@ import java.util.Objects; import java.util.Set; import javax.annotation.Nullable; -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.sonar.java.Preconditions; import org.sonar.java.annotations.Beta; import org.sonar.java.ast.parser.QualifiedIdentifierListTreeImpl; @@ -326,8 +322,6 @@ public static class ImportTreeImpl extends JavaTree implements ImportTree { @Nullable private final SyntaxToken moduleToken; - public IBinding binding; - public ImportTreeImpl(InternalSyntaxToken importToken, @Nullable InternalSyntaxToken staticToken, @Nullable InternalSyntaxToken moduleToken, Tree qualifiedIdentifier, InternalSyntaxToken semiColonToken) { @@ -342,16 +336,6 @@ public ImportTreeImpl(InternalSyntaxToken importToken, @Nullable InternalSyntaxT @Nullable public Symbol symbol() { - if (binding != null) { - switch (binding.getKind()) { - case IBinding.TYPE: - return root.sema.typeSymbol((ITypeBinding) binding); - case IBinding.METHOD: - return root.sema.methodSymbol((IMethodBinding) binding); - case IBinding.VARIABLE: - return root.sema.variableSymbol((IVariableBinding) binding); - } - } return null; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/KeywordSuper.java b/java-frontend/src/main/java/org/sonar/java/model/KeywordSuper.java index 91ec5318ce1..107a80b0f69 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/KeywordSuper.java +++ b/java-frontend/src/main/java/org/sonar/java/model/KeywordSuper.java @@ -16,31 +16,24 @@ */ package org.sonar.java.model; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.java.model.expression.IdentifierTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; -import javax.annotation.Nullable; - final class KeywordSuper extends IdentifierTreeImpl { - KeywordSuper(InternalSyntaxToken token, @Nullable ITypeBinding typeBinding) { + KeywordSuper(InternalSyntaxToken token) { super(token); - this.typeBinding = typeBinding; } @Override public Type symbolType() { - return symbol().type(); + return Type.UNKNOWN; } @Override public Symbol symbol() { - if (typeBinding == null) { - return Symbol.UNKNOWN_SYMBOL; - } - return root.sema.typeSymbol(typeBinding).superSymbol; + return Symbol.UNKNOWN_SYMBOL; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/KeywordThis.java b/java-frontend/src/main/java/org/sonar/java/model/KeywordThis.java index 8d28995961b..785ddc18885 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/KeywordThis.java +++ b/java-frontend/src/main/java/org/sonar/java/model/KeywordThis.java @@ -16,25 +16,18 @@ */ package org.sonar.java.model; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.java.model.expression.IdentifierTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; -import javax.annotation.Nullable; - final class KeywordThis extends IdentifierTreeImpl { - KeywordThis(InternalSyntaxToken token, @Nullable ITypeBinding typeBinding) { + KeywordThis(InternalSyntaxToken token) { super(token); - this.typeBinding = typeBinding; } @Override public Symbol symbol() { - if (typeBinding == null) { - return Symbol.UNKNOWN_SYMBOL; - } - return root.sema.typeSymbol(typeBinding).thisSymbol; + return Symbol.UNKNOWN_SYMBOL; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/TypeParameterTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/TypeParameterTreeImpl.java index d7814d9242b..9035cb6134b 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/TypeParameterTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/TypeParameterTreeImpl.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.List; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.java.ast.parser.QualifiedIdentifierListTreeImpl; import org.sonar.java.model.expression.IdentifierTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; @@ -39,9 +38,6 @@ public class TypeParameterTreeImpl extends JavaTree implements TypeParameterTree private final SyntaxToken extendsToken; private final QualifiedIdentifierListTreeImpl bounds; - @Nullable - ITypeBinding typeBinding; - public TypeParameterTreeImpl(IdentifierTreeImpl identifier) { this(identifier, null, QualifiedIdentifierListTreeImpl.emptyList()); } @@ -90,8 +86,6 @@ public List children() { } public Symbol symbol() { - return typeBinding != null - ? root.sema.typeSymbol(typeBinding) - : Symbol.UNKNOWN_SYMBOL; + return Symbol.UNKNOWN_SYMBOL; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/declaration/ClassTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/declaration/ClassTreeImpl.java index aa7ac772899..63a5872c979 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/declaration/ClassTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/declaration/ClassTreeImpl.java @@ -16,7 +16,6 @@ */ package org.sonar.java.model.declaration; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.java.ast.parser.QualifiedIdentifierListTreeImpl; import org.sonar.java.ast.parser.TypeParameterListTreeImpl; import org.sonarsource.analyzer.commons.collections.ListUtils; @@ -68,8 +67,6 @@ public class ClassTreeImpl extends JavaTree implements ClassTree { @Nullable private SyntaxToken permitsKeyword; private ListTree permittedTypes; - @Nullable - public ITypeBinding typeBinding; public ClassTreeImpl(Kind kind, @Nullable SyntaxToken openBraceToken, List members, @Nullable SyntaxToken closeBraceToken) { this.kind = kind; @@ -218,9 +215,7 @@ public SyntaxToken closeBraceToken() { @Override public Symbol.TypeSymbol symbol() { - return typeBinding != null - ? root.sema.typeSymbol(typeBinding) - : Symbol.TypeSymbol.UNKNOWN_TYPE; + return Symbol.TypeSymbol.UNKNOWN_TYPE; } @Nullable diff --git a/java-frontend/src/main/java/org/sonar/java/model/declaration/MethodTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/declaration/MethodTreeImpl.java index 188637e08ba..849a75389df 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/declaration/MethodTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/declaration/MethodTreeImpl.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Objects; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.IMethodBinding; import org.sonar.java.Preconditions; import org.sonar.java.ast.parser.FormalParametersListTreeImpl; import org.sonar.java.ast.parser.QualifiedIdentifierListTreeImpl; @@ -73,9 +72,6 @@ public class MethodTreeImpl extends JavaTree implements MethodTree { @Nullable private CFG cfg; - @Nullable - public IMethodBinding methodBinding; - /** * Cache for {@link #isOverriding()}. */ @@ -218,9 +214,7 @@ public ExpressionTree defaultValue() { @Override public Symbol.MethodSymbol symbol() { - return methodBinding != null - ? root.sema.methodSymbol(methodBinding) - : Symbol.MethodSymbol.UNKNOWN_METHOD; + return Symbol.MethodSymbol.UNKNOWN_METHOD; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/declaration/VariableTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/declaration/VariableTreeImpl.java index e46d74b113a..a90480080a9 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/declaration/VariableTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/declaration/VariableTreeImpl.java @@ -18,7 +18,6 @@ import java.util.Arrays; import java.util.List; -import org.eclipse.jdt.core.dom.IVariableBinding; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonarsource.analyzer.commons.collections.ListUtils; import org.sonar.java.model.InternalSyntaxToken; @@ -50,8 +49,6 @@ public class VariableTreeImpl extends JavaTree implements VariableTree { @Nullable private SyntaxToken endToken; - public IVariableBinding variableBinding; - public VariableTreeImpl( ModifiersTree modifiers, TypeTree type, @@ -131,9 +128,7 @@ public SyntaxToken equalToken() { @Override public Symbol symbol() { - return variableBinding != null - ? root.sema.variableSymbol(variableBinding) - : Symbol.UNKNOWN_SYMBOL; + return Symbol.UNKNOWN_SYMBOL; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/expression/IdentifierTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/expression/IdentifierTreeImpl.java index 166f6275421..79d9f19b934 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/expression/IdentifierTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/expression/IdentifierTreeImpl.java @@ -19,11 +19,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -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.sonar.java.model.InternalSyntaxToken; import org.sonar.java.model.JLabelSymbol; import org.sonar.java.model.JavaTree; @@ -41,7 +36,6 @@ public class IdentifierTreeImpl extends AssessableExpressionTree implements Iden private final boolean isUnnamedVariable; private List annotations; - public IBinding binding; public JLabelSymbol labelSymbol; public IdentifierTreeImpl(InternalSyntaxToken nameToken) { @@ -81,18 +75,6 @@ public boolean isUnnamedVariable() { @Override public Symbol symbol() { - if (binding != null && !isUnnamedVariable) { - switch (binding.getKind()) { - case IBinding.TYPE: - return root.sema.typeSymbol((ITypeBinding) binding); - case IBinding.METHOD: - return root.sema.methodSymbol((IMethodBinding) binding); - case IBinding.VARIABLE: - return root.sema.variableSymbol((IVariableBinding) binding); - case IBinding.PACKAGE: - return root.sema.packageSymbol((IPackageBinding) binding); - } - } if (labelSymbol != null) { return labelSymbol; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/expression/LambdaExpressionTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/expression/LambdaExpressionTreeImpl.java index d737562cf49..7ce24a9a99a 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/expression/LambdaExpressionTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/expression/LambdaExpressionTreeImpl.java @@ -17,7 +17,6 @@ package org.sonar.java.model.expression; import java.util.Arrays; -import org.eclipse.jdt.core.dom.IMethodBinding; import org.sonar.java.cfg.CFG; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonarsource.analyzer.commons.collections.ListUtils; @@ -45,9 +44,6 @@ public class LambdaExpressionTreeImpl extends AssessableExpressionTree implement private final Tree body; private CFG cfg; - @Nullable - public IMethodBinding methodBinding; - public LambdaExpressionTreeImpl(@Nullable InternalSyntaxToken openParenToken, List parameters, @Nullable InternalSyntaxToken closeParenToken, InternalSyntaxToken arrowToken, Tree body) { this.openParenToken = openParenToken; @@ -91,9 +87,7 @@ public Tree body() { @Override public Symbol.MethodSymbol symbol() { - return methodBinding != null - ? root.sema.methodSymbol(methodBinding) - : Symbol.MethodSymbol.UNKNOWN_METHOD; + return Symbol.MethodSymbol.UNKNOWN_METHOD; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/expression/MethodInvocationTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/expression/MethodInvocationTreeImpl.java index 6a52cb0d654..9899e220d1f 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/expression/MethodInvocationTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/expression/MethodInvocationTreeImpl.java @@ -18,7 +18,6 @@ import java.util.Arrays; import java.util.List; -import org.eclipse.jdt.core.dom.IMethodBinding; import org.sonar.java.ast.parser.ArgumentListTreeImpl; import org.sonarsource.analyzer.commons.collections.ListUtils; import org.sonar.plugins.java.api.semantic.Symbol; @@ -42,9 +41,6 @@ public class MethodInvocationTreeImpl extends AssessableExpressionTree implement @Nullable private TypeArguments typeArguments; - @Nullable - public IMethodBinding methodBinding; - public MethodInvocationTreeImpl(ExpressionTree methodSelect, @Nullable TypeArguments typeArguments, ArgumentListTreeImpl arguments) { this.methodSelect = Objects.requireNonNull(methodSelect); this.typeArguments = typeArguments; @@ -91,9 +87,7 @@ public Symbol symbol() { @Override public Symbol.MethodSymbol methodSymbol() { - return methodBinding != null - ? root.sema.methodSymbol(methodBinding) - : Symbol.MethodSymbol.UNKNOWN_METHOD; + return Symbol.MethodSymbol.UNKNOWN_METHOD; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/AbstractPatternTree.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/AbstractPatternTree.java index 183d239479f..01573d1c43e 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/AbstractPatternTree.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/AbstractPatternTree.java @@ -17,8 +17,6 @@ package org.sonar.java.model.pattern; import java.util.Optional; -import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.java.model.JavaTree; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.PatternTree; @@ -31,19 +29,13 @@ public abstract class AbstractPatternTree extends JavaTree implements PatternTre private final Tree.Kind kind; - @Nullable - public ITypeBinding typeBinding; - - AbstractPatternTree(Tree.Kind kind, @Nullable ITypeBinding typeBinding) { + AbstractPatternTree(Tree.Kind kind) { this.kind = kind; - this.typeBinding = typeBinding; } @Override public Type symbolType() { - return typeBinding != null - ? root.sema.type(typeBinding) - : Type.UNKNOWN; + return Type.UNKNOWN; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/DefaultPatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/DefaultPatternTreeImpl.java index 797b890d3f3..6335db83326 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/DefaultPatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/DefaultPatternTreeImpl.java @@ -18,8 +18,6 @@ import java.util.Collections; import java.util.List; -import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.plugins.java.api.tree.DefaultPatternTree; import org.sonar.plugins.java.api.tree.SyntaxToken; import org.sonar.plugins.java.api.tree.Tree; @@ -32,8 +30,8 @@ public class DefaultPatternTreeImpl extends AbstractPatternTree implements Defau private final SyntaxToken defaultToken; - public DefaultPatternTreeImpl(SyntaxToken defaultToken, @Nullable ITypeBinding typeBinding) { - super(Tree.Kind.DEFAULT_PATTERN, typeBinding); + public DefaultPatternTreeImpl(SyntaxToken defaultToken) { + super(Tree.Kind.DEFAULT_PATTERN); this.defaultToken = defaultToken; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/GuardedPatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/GuardedPatternTreeImpl.java index af42ddfcdbf..29dc286ce23 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/GuardedPatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/GuardedPatternTreeImpl.java @@ -18,8 +18,6 @@ import java.util.Arrays; import java.util.List; -import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.GuardedPatternTree; import org.sonar.plugins.java.api.tree.PatternTree; @@ -36,8 +34,8 @@ public class GuardedPatternTreeImpl extends AbstractPatternTree implements Guard private final SyntaxToken whenOperator; private final ExpressionTree expression; - public GuardedPatternTreeImpl(PatternTree pattern, SyntaxToken whenOperator, ExpressionTree expression, @Nullable ITypeBinding typeBinding) { - super(Tree.Kind.GUARDED_PATTERN, typeBinding); + public GuardedPatternTreeImpl(PatternTree pattern, SyntaxToken whenOperator, ExpressionTree expression) { + super(Tree.Kind.GUARDED_PATTERN); this.pattern = pattern; this.whenOperator = whenOperator; this.expression = expression; diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/NullPatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/NullPatternTreeImpl.java index 665eb9e0d2b..328f96ca56c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/NullPatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/NullPatternTreeImpl.java @@ -33,7 +33,7 @@ public class NullPatternTreeImpl extends AbstractPatternTree implements NullPatt private final LiteralTree nullLiteral; public NullPatternTreeImpl(LiteralTree nullLiteral) { - super(Tree.Kind.NULL_PATTERN, null); + super(Tree.Kind.NULL_PATTERN); this.nullLiteral = nullLiteral; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/RecordPatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/RecordPatternTreeImpl.java index 93e10579aa2..bbd858dd401 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/RecordPatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/RecordPatternTreeImpl.java @@ -38,7 +38,7 @@ public class RecordPatternTreeImpl extends AbstractPatternTree implements Record private final InternalSyntaxToken closeParenToken; public RecordPatternTreeImpl(TypeTree type, InternalSyntaxToken openParenToken, List patterns, InternalSyntaxToken closeParenToken) { - super(Kind.RECORD_PATTERN, null); + super(Kind.RECORD_PATTERN); this.type = type; this.openParenToken = openParenToken; this.patterns = patterns; diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/TypePatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/TypePatternTreeImpl.java index afc45f2b891..1a53a36bcf3 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/TypePatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/TypePatternTreeImpl.java @@ -18,8 +18,6 @@ import java.util.Collections; import java.util.List; -import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TreeVisitor; import org.sonar.plugins.java.api.tree.TypePatternTree; @@ -32,8 +30,8 @@ public class TypePatternTreeImpl extends AbstractPatternTree implements TypePatt private final VariableTree patternVariable; - public TypePatternTreeImpl(VariableTree patternVariable, @Nullable ITypeBinding typeBinding) { - super(Tree.Kind.TYPE_PATTERN, typeBinding); + public TypePatternTreeImpl(VariableTree patternVariable) { + super(Tree.Kind.TYPE_PATTERN); this.patternVariable = patternVariable; } diff --git a/java-frontend/src/main/java/org/sonar/java/semsitter/SemsitterBridge.java b/java-frontend/src/main/java/org/sonar/java/semsitter/SemsitterBridge.java new file mode 100644 index 00000000000..35285a54aeb --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/semsitter/SemsitterBridge.java @@ -0,0 +1,164 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.semsitter; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +/** + * JSONL stdin/stdout client for the {@code semsitter-bridge} executable. + * + *

One JSON object per line. A parse request is + * {@code {"id":1,"op":"parse","source":"hello"}} and the reply is one line + * containing {@code id}, {@code ok}, {@code hasError} and {@code tree}. + * Tree positions are tree-sitter's 0-based rows and columns. + * The executable is supplied by the caller. This class does not embed a binary + * or choose one for the current operating system. + */ +public final class SemsitterBridge implements AutoCloseable { + + private static final int STDERR_LIMIT = 8192; + + private final Process process; + private final BufferedWriter toBridge; + private final BufferedReader fromBridge; + private final StringBuilder stderr = new StringBuilder(); + private int nextId = 1; + private boolean closed; + + private SemsitterBridge(Process process) { + this.process = process; + this.toBridge = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8)); + this.fromBridge = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); + Thread drain = new Thread(() -> drain(process.getErrorStream(), stderr), "semsitter-bridge-stderr"); + drain.setDaemon(true); + drain.start(); + } + + public static SemsitterBridge start(Path executable) throws IOException { + Process process = new ProcessBuilder(executable.toAbsolutePath().toString()).start(); + return new SemsitterBridge(process); + } + + /** + * @return the raw response line, so a caller can log the exact JSON the bridge produced + */ + public synchronized String parse(String source) throws IOException { + JsonObject request = new JsonObject(); + request.addProperty("id", nextId); + request.addProperty("op", "parse"); + request.addProperty("source", source); + nextId++; + return exchange(request); + } + + @Override + public synchronized void close() { + if (closed) { + return; + } + closed = true; + try { + JsonObject request = new JsonObject(); + request.addProperty("id", nextId); + request.addProperty("op", "quit"); + nextId++; + exchange(request); + } catch (IOException ignored) { + process.destroy(); + } + try { + toBridge.close(); + } catch (IOException ignored) { + process.destroy(); + } + process.destroy(); + try { + if (!process.waitFor(2, TimeUnit.SECONDS)) { + process.destroyForcibly(); + } + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + process.destroyForcibly(); + } + } + + private String exchange(JsonObject request) throws IOException { + int id = request.get("id").getAsInt(); + toBridge.write(request.toString()); + toBridge.write('\n'); + toBridge.flush(); + String responseLine = fromBridge.readLine(); + if (responseLine == null) { + throw new IOException("semsitter bridge closed stdout" + stderrSuffix()); + } + JsonElement parsed; + try { + parsed = JsonParser.parseString(responseLine); + } catch (RuntimeException invalid) { + throw new IOException("semsitter bridge returned invalid JSON: " + responseLine + stderrSuffix(), invalid); + } + if (!parsed.isJsonObject()) { + throw new IOException("semsitter bridge returned a non-object: " + responseLine + stderrSuffix()); + } + JsonObject response = parsed.getAsJsonObject(); + if (response.has("id") && response.get("id").isJsonPrimitive() && response.get("id").getAsJsonPrimitive().isNumber() + && response.get("id").getAsInt() != id) { + throw new IOException("semsitter bridge response id mismatch: " + responseLine); + } + if (!response.has("ok") || !response.get("ok").getAsBoolean()) { + String error = response.has("error") ? response.get("error").getAsString() : responseLine; + throw new IOException("semsitter bridge: " + error + stderrSuffix()); + } + return responseLine; + } + + private String stderrSuffix() { + synchronized (stderr) { + if (stderr.isEmpty()) { + return ""; + } + return " stderr: " + stderr; + } + } + + private static void drain(InputStream stream, StringBuilder buffer) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + synchronized (buffer) { + buffer.append(line).append('\n'); + if (buffer.length() > STDERR_LIMIT) { + buffer.delete(0, buffer.length() - STDERR_LIMIT); + } + } + } + } catch (IOException ignored) { + // The process closed stderr. + } + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/semsitter/SemsitterBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/semsitter/SemsitterBridgeTest.java new file mode 100644 index 00000000000..15eae240f59 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/semsitter/SemsitterBridgeTest.java @@ -0,0 +1,97 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.semsitter; + +import com.google.gson.JsonParser; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +@DisabledOnOs(OS.WINDOWS) +class SemsitterBridgeTest { + + @Test + void parse_returns_the_jsonl_response() throws IOException { + Path script = fakeBridge(""" + #!/bin/sh + while IFS= read -r line; do + case "$line" in + *'"op":"quit"'*) printf '%s\\n' '{"id":2,"ok":true}'; exit 0 ;; + *) printf '%s\\n' '{"id":1,"ok":true,"hasError":false,"tree":{"kind":"source_file","children":[{"kind":"hello"}]}}' ;; + esac + done + """); + try (SemsitterBridge bridge = SemsitterBridge.start(script)) { + String response = bridge.parse("hello"); + assertThat(JsonParser.parseString(response).getAsJsonObject().getAsJsonObject("tree").get("kind").getAsString()) + .isEqualTo("source_file"); + } finally { + Files.deleteIfExists(script); + } + } + + @Test + void parse_hello_with_the_real_bridge() throws IOException { + String executable = System.getenv("SEMSITTER_BRIDGE"); + assumeTrue(executable != null && Files.isExecutable(Path.of(executable))); + try (SemsitterBridge bridge = SemsitterBridge.start(Path.of(executable))) { + String hello = bridge.parse("hello"); + assertThat(JsonParser.parseString(hello).getAsJsonObject().getAsJsonObject("tree").get("kind").getAsString()) + .isEqualTo("source_file"); + String broken = bridge.parse("nope"); + assertThat(JsonParser.parseString(broken).getAsJsonObject().get("hasError").getAsBoolean()).isTrue(); + } + } + + @Test + void parse_reports_a_bridge_error() throws IOException { + Path script = fakeBridge(""" + #!/bin/sh + while IFS= read -r line; do + case "$line" in + *'"op":"quit"'*) printf '%s\\n' '{"id":2,"ok":true}'; exit 0 ;; + *) printf '%s\\n' '{"id":1,"ok":false,"error":"boom"}' ;; + esac + done + """); + try (SemsitterBridge bridge = SemsitterBridge.start(script)) { + assertThatThrownBy(() -> bridge.parse("hello")) + .isInstanceOf(IOException.class) + .hasMessageContaining("boom"); + } finally { + Files.deleteIfExists(script); + } + } + + private static Path fakeBridge(String script) throws IOException { + Path path = Files.createTempFile("semsitter-bridge", ".sh"); + Files.writeString(path, script); + Files.setPosixFilePermissions(path, Set.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)); + return path; + } +} diff --git a/semsitter-sample/.gitignore b/semsitter-sample/.gitignore new file mode 100644 index 00000000000..ea8c4bf7f35 --- /dev/null +++ b/semsitter-sample/.gitignore @@ -0,0 +1 @@ +/target diff --git a/semsitter-sample/Cargo.lock b/semsitter-sample/Cargo.lock new file mode 100644 index 00000000000..2a6b897f8b6 --- /dev/null +++ b/semsitter-sample/Cargo.lock @@ -0,0 +1,598 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "cc" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "schemars" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c3a92094fa7d61aa124645844facb6b554dfc797136d0f5fd1f890e2bffc69" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.9", +] + +[[package]] +name = "semsitter-sample" +version = "0.1.0" +dependencies = [ + "cc", + "serde_json", + "tree-sitter", + "tree-sitter-language", + "udg-model", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "foldhash", + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tree-sitter" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5129de2ccc97808c4583d48f1fb44576d015cf241e432800572bda11ef30c3b" +dependencies = [ + "cc", + "regex", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0af592be68c579aa78a16846bd19422978c3c52e438523d45ff5d1bff1f9d4a" + +[[package]] +name = "udg-model" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=d606e04b766dc4f506c20c3c37f26d1511b5f065#d606e04b766dc4f506c20c3c37f26d1511b5f065" +dependencies = [ + "clap", + "rustc-hash", + "schemars", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/semsitter-sample/Cargo.toml b/semsitter-sample/Cargo.toml new file mode 100644 index 00000000000..aa45e93d282 --- /dev/null +++ b/semsitter-sample/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "semsitter-sample" +version = "0.1.0" +edition = "2024" +publish = false +description = "PoC crate: one tree-sitter grammar rule linked against semsitter udg-model" + +[dependencies] +serde_json = "1" +tree-sitter = "0.27" +tree-sitter-language = "0.1" +udg-model = { git = "ssh://git@github.com/SonarSource/semsitter.git", rev = "d606e04b766dc4f506c20c3c37f26d1511b5f065" } + +[dev-dependencies] +serde_json = "1" + +[build-dependencies] +cc = "1" diff --git a/java-frontend/src/test/java/org/eclipse/jdt/core/dom/ASTUtilsTest.java b/semsitter-sample/build.rs similarity index 70% rename from java-frontend/src/test/java/org/eclipse/jdt/core/dom/ASTUtilsTest.java rename to semsitter-sample/build.rs index 7e985fc8e8a..51452ae0f9c 100644 --- a/java-frontend/src/test/java/org/eclipse/jdt/core/dom/ASTUtilsTest.java +++ b/semsitter-sample/build.rs @@ -14,16 +14,12 @@ * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ -package org.eclipse.jdt.core.dom; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertNull; - -class ASTUtilsTest { - - @Test - void type_resolution_should_recover() { - assertNull(ASTUtils.resolveType(/* causes NPE */null, "Unknown")); - } +fn main() { + let src_dir = std::path::Path::new("src"); + let mut c_config = cc::Build::new(); + c_config.std("c11").include(src_dir); + c_config.file(src_dir.join("parser.c")); + println!("cargo:rerun-if-changed=src/parser.c"); + c_config.compile("tree-sitter-sample"); } diff --git a/semsitter-sample/grammar.js b/semsitter-sample/grammar.js new file mode 100644 index 00000000000..bf52f917126 --- /dev/null +++ b/semsitter-sample/grammar.js @@ -0,0 +1,8 @@ +/// + +module.exports = grammar({ + name: "sample", + rules: { + source_file: ($) => "hello", + }, +}); diff --git a/semsitter-sample/rust-toolchain.toml b/semsitter-sample/rust-toolchain.toml new file mode 100644 index 00000000000..aa20bc990d6 --- /dev/null +++ b/semsitter-sample/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.98.0" +components = ["rustfmt", "clippy"] diff --git a/java-frontend/src/main/java/org/eclipse/jdt/core/dom/package-info.java b/semsitter-sample/src/bin/semsitter-bridge.rs similarity index 54% rename from java-frontend/src/main/java/org/eclipse/jdt/core/dom/package-info.java rename to semsitter-sample/src/bin/semsitter-bridge.rs index 41ae9dc837b..195dc83387e 100644 --- a/java-frontend/src/main/java/org/eclipse/jdt/core/dom/package-info.java +++ b/semsitter-sample/src/bin/semsitter-bridge.rs @@ -14,4 +14,25 @@ * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ -package org.eclipse.jdt.core.dom; + +use std::io::{BufRead, BufReader, Write}; + +fn main() { + let stdin = BufReader::new(std::io::stdin()); + let mut stdout = std::io::BufWriter::new(std::io::stdout()); + for line in stdin.lines() { + let Ok(line) = line else { + break; + }; + if line.is_empty() { + continue; + } + let (response, quit) = semsitter_sample::handle_line(&line); + if writeln!(stdout, "{response}").is_err() || stdout.flush().is_err() { + break; + } + if quit { + break; + } + } +} diff --git a/semsitter-sample/src/grammar.json b/semsitter-sample/src/grammar.json new file mode 100644 index 00000000000..357098696d4 --- /dev/null +++ b/semsitter-sample/src/grammar.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/grammar.schema.json", + "name": "sample", + "rules": { + "source_file": { + "type": "STRING", + "value": "hello" + } + }, + "extras": [ + { + "type": "PATTERN", + "value": "\\s" + } + ], + "conflicts": [], + "precedences": [], + "externals": [], + "inline": [], + "supertypes": [], + "reserved": {} +} \ No newline at end of file diff --git a/semsitter-sample/src/lib.rs b/semsitter-sample/src/lib.rs new file mode 100644 index 00000000000..cf4a3769904 --- /dev/null +++ b/semsitter-sample/src/lib.rs @@ -0,0 +1,182 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +use tree_sitter_language::LanguageFn; + +unsafe extern "C" { + fn tree_sitter_sample() -> *const (); +} + +pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_sample) }; + +pub fn language() -> tree_sitter::Language { + LANGUAGE.into() +} + +/// Handles one JSONL request. +/// +/// Stdout of `semsitter-bridge` is response lines only. A request is +/// `{"id":1,"op":"parse","source":"..."}` or `{"id":2,"op":"quit"}`. +/// A parse response is `{"id":1,"ok":true,"hasError":false,"tree":{...}}`. +/// Tree positions are tree-sitter's 0-based rows and columns. +/// The boolean is true when the process should exit after writing the response. +pub fn handle_line(line: &str) -> (String, bool) { + let request: serde_json::Value = match serde_json::from_str::(line) { + Ok(value) if value.is_object() => value, + Ok(_) => return (error_response(serde_json::Value::Null, "request must be an object"), false), + Err(error) => return (error_response(serde_json::Value::Null, &error.to_string()), false), + }; + let id = request.get("id").cloned().unwrap_or(serde_json::Value::Null); + match request.get("op").and_then(serde_json::Value::as_str) { + Some("quit") => (ok_response(id, None, None), true), + Some("parse") => match request.get("source").and_then(serde_json::Value::as_str) { + Some(source) => match parse_source(source) { + Ok((tree, has_error)) => (ok_response(id, Some(tree), Some(has_error)), false), + Err(error) => (error_response(id, &error), false), + }, + None => (error_response(id, "missing source"), false), + }, + Some(other) => (error_response(id, &format!("unknown op {other}")), false), + None => (error_response(id, "missing op"), false), + } +} + +fn parse_source(source: &str) -> Result<(serde_json::Value, bool), String> { + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&language()) + .map_err(|error| error.to_string())?; + let tree = parser.parse(source, None).ok_or("parser returned no tree")?; + let root = tree.root_node(); + Ok((node_json(root, 0), root.has_error())) +} + +fn node_json(node: tree_sitter::Node, depth: usize) -> serde_json::Value { + if depth > 64 { + return serde_json::json!({"truncated": true}); + } + let start = node.start_position(); + let end = node.end_position(); + let child_count = node.child_count(); + let mut children = Vec::with_capacity(usize::try_from(child_count).unwrap_or(0)); + for index in 0..child_count { + if let Some(child) = node.child(index) { + children.push(node_json(child, depth + 1)); + } + } + serde_json::json!({ + "kind": node.kind(), + "named": node.is_named(), + "error": node.is_error(), + "missing": node.is_missing(), + "startByte": node.start_byte(), + "endByte": node.end_byte(), + "startRow": start.row, + "startColumn": start.column, + "endRow": end.row, + "endColumn": end.column, + "children": children, + }) +} + +fn ok_response(id: serde_json::Value, tree: Option, has_error: Option) -> String { + let mut response = serde_json::Map::new(); + response.insert("id".to_string(), id); + response.insert("ok".to_string(), serde_json::Value::Bool(true)); + if let Some(has_error) = has_error { + response.insert("hasError".to_string(), serde_json::Value::Bool(has_error)); + } + if let Some(tree) = tree { + response.insert("tree".to_string(), tree); + } + serde_json::Value::Object(response).to_string() +} + +fn error_response(id: serde_json::Value, error: &str) -> String { + serde_json::json!({ + "id": id, + "ok": false, + "error": error, + }) + .to_string() +} + +#[cfg(test)] +mod tests { + use udg_model::location_index::LineRange; + use udg_model::syntax_unit::{SyntaxSpan, SyntaxUnit}; + + use super::language; + + #[test] + fn sample_rule_parses_hello() { + let mut parser = tree_sitter::Parser::new(); + parser.set_language(&language()).unwrap(); + + let tree = parser.parse("hello", None).unwrap(); + let root = tree.root_node(); + assert_eq!(root.kind(), "source_file"); + assert!(!root.has_error()); + assert_eq!(root.child(0).unwrap().kind(), "hello"); + + let unit = SyntaxUnit { + span: SyntaxSpan { + syntax_kind: root.kind().to_string(), + lines: LineRange::point(1), + }, + nested: Vec::new(), + }; + assert_eq!(unit.span.syntax_kind, "source_file"); + assert_eq!(unit.span.lines, LineRange::point(1)); + } + + #[test] + fn jsonl_parse_hello() { + let (response, quit) = super::handle_line(r#"{"id":7,"op":"parse","source":"hello"}"#); + assert!(!quit); + let json: serde_json::Value = serde_json::from_str(&response).unwrap(); + assert_eq!(json["id"], 7); + assert_eq!(json["ok"], true); + assert_eq!(json["hasError"], false); + assert_eq!(json["tree"]["kind"], "source_file"); + assert_eq!(json["tree"]["children"][0]["kind"], "hello"); + } + + #[test] + fn jsonl_keeps_an_error_tree() { + let (response, quit) = super::handle_line(r#"{"id":1,"op":"parse","source":"nope"}"#); + assert!(!quit); + let json: serde_json::Value = serde_json::from_str(&response).unwrap(); + assert_eq!(json["ok"], true); + assert_eq!(json["hasError"], true); + assert_eq!(json["tree"]["kind"], "ERROR"); + } + + #[test] + fn jsonl_quit_and_bad_request() { + let (response, quit) = super::handle_line(r#"{"id":2,"op":"quit"}"#); + assert!(quit); + let json: serde_json::Value = serde_json::from_str(&response).unwrap(); + assert_eq!(json["ok"], true); + + let (response, quit) = super::handle_line("not-json"); + assert!(!quit); + let json: serde_json::Value = serde_json::from_str(&response).unwrap(); + assert_eq!(json["ok"], false); + assert!(json["error"].as_str().unwrap().contains("expected")); + } +} diff --git a/semsitter-sample/src/node-types.json b/semsitter-sample/src/node-types.json new file mode 100644 index 00000000000..cd69f6eeb65 --- /dev/null +++ b/semsitter-sample/src/node-types.json @@ -0,0 +1,12 @@ +[ + { + "type": "source_file", + "named": true, + "root": true, + "fields": {} + }, + { + "type": "hello", + "named": false + } +] \ No newline at end of file diff --git a/semsitter-sample/src/parser.c b/semsitter-sample/src/parser.c new file mode 100644 index 00000000000..25cd1729d5f --- /dev/null +++ b/semsitter-sample/src/parser.c @@ -0,0 +1,190 @@ +/* Automatically @generated by tree-sitter */ + +#include "tree_sitter/parser.h" + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#define LANGUAGE_VERSION 15 +#define STATE_COUNT 4 +#define LARGE_STATE_COUNT 2 +#define SYMBOL_COUNT 3 +#define ALIAS_COUNT 0 +#define TOKEN_COUNT 2 +#define EXTERNAL_TOKEN_COUNT 0 +#define FIELD_COUNT 0 +#define MAX_ALIAS_SEQUENCE_LENGTH 1 +#define MAX_RESERVED_WORD_SET_SIZE 0 +#define PRODUCTION_ID_COUNT 1 +#define SUPERTYPE_COUNT 0 + +enum ts_symbol_identifiers { + anon_sym_hello = 1, + sym_source_file = 2, +}; + +static const char * const ts_symbol_names[] = { + [ts_builtin_sym_end] = "end", + [anon_sym_hello] = "hello", + [sym_source_file] = "source_file", +}; + +static const TSSymbol ts_symbol_map[] = { + [ts_builtin_sym_end] = ts_builtin_sym_end, + [anon_sym_hello] = anon_sym_hello, + [sym_source_file] = sym_source_file, +}; + +static const TSSymbolMetadata ts_symbol_metadata[] = { + [ts_builtin_sym_end] = { + .visible = false, + .named = true, + }, + [anon_sym_hello] = { + .visible = true, + .named = false, + }, + [sym_source_file] = { + .visible = true, + .named = true, + }, +}; + +static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = { + [0] = {0}, +}; + +static const uint16_t ts_non_terminal_alias_map[] = { + 0, +}; + +static const TSStateId ts_primary_state_ids[STATE_COUNT] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, +}; + +static bool ts_lex(TSLexer *lexer, TSStateId state) { + START_LEXER(); + eof = lexer->eof(lexer); + switch (state) { + case 0: + if (eof) ADVANCE(5); + if (lookahead == 'h') ADVANCE(1); + if (('\t' <= lookahead && lookahead <= '\r') || + lookahead == ' ') SKIP(0); + END_STATE(); + case 1: + if (lookahead == 'e') ADVANCE(3); + END_STATE(); + case 2: + if (lookahead == 'l') ADVANCE(4); + END_STATE(); + case 3: + if (lookahead == 'l') ADVANCE(2); + END_STATE(); + case 4: + if (lookahead == 'o') ADVANCE(6); + END_STATE(); + case 5: + ACCEPT_TOKEN(ts_builtin_sym_end); + END_STATE(); + case 6: + ACCEPT_TOKEN(anon_sym_hello); + END_STATE(); + default: + return false; + } +} + +static const TSLexerMode ts_lex_modes[STATE_COUNT] = { + [0] = {.lex_state = 0}, + [1] = {.lex_state = 0}, + [2] = {.lex_state = 0}, + [3] = {.lex_state = 0}, +}; + +static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { + [STATE(0)] = { + [ts_builtin_sym_end] = ACTIONS(1), + [anon_sym_hello] = ACTIONS(1), + }, + [STATE(1)] = { + [sym_source_file] = STATE(3), + [anon_sym_hello] = ACTIONS(3), + }, +}; + +static const uint16_t ts_small_parse_table[] = { + [0] = 1, + ACTIONS(5), 1, + ts_builtin_sym_end, + [4] = 1, + ACTIONS(7), 1, + ts_builtin_sym_end, +}; + +static const uint32_t ts_small_parse_table_map[] = { + [SMALL_STATE(2)] = 0, + [SMALL_STATE(3)] = 4, +}; + +static const TSParseActionEntry ts_parse_actions[] = { + [0] = {.entry = {.count = 0, .reusable = false}}, + [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(), + [3] = {.entry = {.count = 1, .reusable = true}}, SHIFT(2), + [5] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 1, 0, 0), + [7] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(), +}; + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef TREE_SITTER_HIDE_SYMBOLS +#define TS_PUBLIC +#elif defined(_WIN32) +#define TS_PUBLIC __declspec(dllexport) +#else +#define TS_PUBLIC __attribute__((visibility("default"))) +#endif + +TS_PUBLIC const TSLanguage *tree_sitter_sample(void) { + static const TSLanguage language = { + .abi_version = LANGUAGE_VERSION, + .symbol_count = SYMBOL_COUNT, + .alias_count = ALIAS_COUNT, + .token_count = TOKEN_COUNT, + .external_token_count = EXTERNAL_TOKEN_COUNT, + .state_count = STATE_COUNT, + .large_state_count = LARGE_STATE_COUNT, + .production_id_count = PRODUCTION_ID_COUNT, + .supertype_count = SUPERTYPE_COUNT, + .field_count = FIELD_COUNT, + .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, + .parse_table = &ts_parse_table[0][0], + .small_parse_table = ts_small_parse_table, + .small_parse_table_map = ts_small_parse_table_map, + .parse_actions = ts_parse_actions, + .symbol_names = ts_symbol_names, + .symbol_metadata = ts_symbol_metadata, + .public_symbol_map = ts_symbol_map, + .alias_map = ts_non_terminal_alias_map, + .alias_sequences = &ts_alias_sequences[0][0], + .lex_modes = (const void*)ts_lex_modes, + .lex_fn = ts_lex, + .primary_state_ids = ts_primary_state_ids, + .name = "sample", + .max_reserved_word_set_size = 0, + .metadata = { + .major_version = 0, + .minor_version = 1, + .patch_version = 0, + }, + }; + return &language; +} +#ifdef __cplusplus +} +#endif diff --git a/semsitter-sample/src/tree_sitter/alloc.h b/semsitter-sample/src/tree_sitter/alloc.h new file mode 100644 index 00000000000..1abdd120157 --- /dev/null +++ b/semsitter-sample/src/tree_sitter/alloc.h @@ -0,0 +1,54 @@ +#ifndef TREE_SITTER_ALLOC_H_ +#define TREE_SITTER_ALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +// Allow clients to override allocation functions +#ifdef TREE_SITTER_REUSE_ALLOCATOR + +extern void *(*ts_current_malloc)(size_t size); +extern void *(*ts_current_calloc)(size_t count, size_t size); +extern void *(*ts_current_realloc)(void *ptr, size_t size); +extern void (*ts_current_free)(void *ptr); + +#ifndef ts_malloc +#define ts_malloc ts_current_malloc +#endif +#ifndef ts_calloc +#define ts_calloc ts_current_calloc +#endif +#ifndef ts_realloc +#define ts_realloc ts_current_realloc +#endif +#ifndef ts_free +#define ts_free ts_current_free +#endif + +#else + +#ifndef ts_malloc +#define ts_malloc malloc +#endif +#ifndef ts_calloc +#define ts_calloc calloc +#endif +#ifndef ts_realloc +#define ts_realloc realloc +#endif +#ifndef ts_free +#define ts_free free +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ALLOC_H_ diff --git a/semsitter-sample/src/tree_sitter/array.h b/semsitter-sample/src/tree_sitter/array.h new file mode 100644 index 00000000000..eba3da4062c --- /dev/null +++ b/semsitter-sample/src/tree_sitter/array.h @@ -0,0 +1,336 @@ +#ifndef TREE_SITTER_ARRAY_H_ +#define TREE_SITTER_ARRAY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./alloc.h" + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4101) +#elif defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#define Array(T) \ + struct { \ + T *contents; \ + uint32_t size; \ + uint32_t capacity; \ + } + +/// Initialize an array. +#define array_init(self) \ + ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) + +/// Create an empty array. +#define array_new() \ + { NULL, 0, 0 } + +/// Get a pointer to the element at a given `index` in the array. +#define array_get(self, _index) \ + (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index]) + +/// Get a pointer to the first element in the array. +#define array_front(self) array_get(self, 0) + +/// Get a pointer to the last element in the array. +#define array_back(self) array_get(self, (self)->size - 1) + +/// Clear the array, setting its size to zero. Note that this does not free any +/// memory allocated for the array's contents. +#define array_clear(self) ((self)->size = 0) + +#ifdef __cplusplus +#define _array__cast(self, expr) (decltype((self)->contents))(expr) +#else +#define _array__cast(self, expr) (expr) +#endif + +/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is +/// less than the array's current capacity, this function has no effect. +#define array_reserve(self, new_capacity) \ + ((self)->contents = _array__cast(self, _array__reserve( \ + (void *)(self)->contents, &(self)->capacity, \ + array_elem_size(self), new_capacity)) \ + ) + +/// Free any memory allocated for this array. Note that this does not free any +/// memory allocated for the array's contents. +#define array_delete(self) \ + do { \ + if ((self)->contents) ts_free((self)->contents); \ + (self)->contents = NULL; \ + (self)->size = 0; \ + (self)->capacity = 0; \ + } while (0) + +/// Push a new `element` onto the end of the array. +#define array_push(self, element) \ + do { \ + (self)->contents = _array__cast(self, _array__grow( \ + (void *)(self)->contents, (self)->size, &(self)->capacity, \ + 1, array_elem_size(self) \ + )); \ + (self)->contents[(self)->size++] = (element); \ + } while(0) + +/// Increase the array's size by `count` elements. +/// New elements are zero-initialized. +#define array_grow_by(self, count) \ + do { \ + if ((count) == 0) break; \ + (self)->contents = _array__cast(self, _array__grow( \ + (self)->contents, (self)->size, &(self)->capacity, \ + count, array_elem_size(self) \ + )); \ + memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \ + (self)->size += (count); \ + } while (0) + +/// Append all elements from one array to the end of another. +#define array_push_all(self, other) \ + array_extend((self), (other)->size, (other)->contents) + +/// Append `count` elements to the end of the array, reading their values from the +/// `contents` pointer. +#define array_extend(self, count, other_contents) \ + ((self)->contents = _array__cast(self, _array__splice( \ + (void*)(self)->contents, &(self)->size, &(self)->capacity, \ + array_elem_size(self), (self)->size, 0, count, other_contents \ + ))) + +/// Remove `old_count` elements from the array starting at the given `index`. At +/// the same index, insert `new_count` new elements, reading their values from the +/// `new_contents` pointer. +#define array_splice(self, _index, old_count, new_count, new_contents) \ + ((self)->contents = _array__cast(self, _array__splice( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + array_elem_size(self), _index, old_count, new_count, new_contents \ + ))) + +/// Insert one `element` into the array at the given `index`. +#define array_insert(self, _index, element) \ + ((self)->contents = _array__cast(self, _array__splice( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + array_elem_size(self), _index, 0, 1, &(element) \ + ))) + +/// Remove one element from the array at the given `index`. +#define array_erase(self, _index) \ + _array__erase((void *)(self)->contents, &(self)->size, array_elem_size(self), _index) + +/// Pop the last element off the array, returning the element by value. +#define array_pop(self) ((self)->contents[--(self)->size]) + +/// Assign the contents of one array to another, reallocating if necessary. +#define array_assign(self, other) \ + ((self)->contents = _array__cast(self, _array__assign( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + (const void *)(other)->contents, (other)->size, array_elem_size(self) \ + ))) + +/// Swap one array with another +#define array_swap(self, other) \ + do { \ + void *_array_swap_tmp = (void *)(self)->contents; \ + (self)->contents = (other)->contents; \ + (other)->contents = _array__cast(other, _array_swap_tmp); \ + _array__swap(&(self)->size, &(self)->capacity, \ + &(other)->size, &(other)->capacity); \ + } while (0) + +/// Get the size of the array contents +#define array_elem_size(self) (sizeof *(self)->contents) + +/// Search a sorted array for a given `needle` value, using the given `compare` +/// callback to determine the order. +/// +/// If an existing element is found to be equal to `needle`, then the `index` +/// out-parameter is set to the existing value's index, and the `exists` +/// out-parameter is set to true. Otherwise, `index` is set to an index where +/// `needle` should be inserted in order to preserve the sorting, and `exists` +/// is set to false. +#define array_search_sorted_with(self, compare, needle, _index, _exists) \ + _array__search_sorted(self, 0, compare, , needle, _index, _exists) + +/// Search a sorted array for a given `needle` value, using integer comparisons +/// of a given struct field (specified with a leading dot) to determine the order. +/// +/// See also `array_search_sorted_with`. +#define array_search_sorted_by(self, field, needle, _index, _exists) \ + _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists) + +/// Insert a given `value` into a sorted array, using the given `compare` +/// callback to determine the order. +#define array_insert_sorted_with(self, compare, value) \ + do { \ + unsigned _index, _exists; \ + array_search_sorted_with(self, compare, &(value), &_index, &_exists); \ + if (!_exists) array_insert(self, _index, value); \ + } while (0) + +/// Insert a given `value` into a sorted array, using integer comparisons of +/// a given struct field (specified with a leading dot) to determine the order. +/// +/// See also `array_search_sorted_by`. +#define array_insert_sorted_by(self, field, value) \ + do { \ + unsigned _index, _exists; \ + array_search_sorted_by(self, field, (value) field, &_index, &_exists); \ + if (!_exists) array_insert(self, _index, value); \ + } while (0) + +// Private + +// Pointers to individual `Array` fields (rather than the entire `Array` itself) +// are passed to the various `_array__*` functions below to address strict aliasing +// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`. +// +// The `Array` type itself was not altered as a solution in order to avoid breakage +// with existing consumers (in particular, parsers with external scanners). + +/// This is not what you're looking for, see `array_erase`. +static inline void _array__erase(void* self_contents, uint32_t *size, + size_t element_size, uint32_t index) { + assert(index < *size); + char *contents = (char *)self_contents; + memmove(contents + index * element_size, contents + (index + 1) * element_size, + (*size - index - 1) * element_size); + (*size)--; +} + +/// This is not what you're looking for, see `array_reserve`. +static inline void *_array__reserve(void *contents, uint32_t *capacity, + size_t element_size, uint32_t new_capacity) { + void *new_contents = contents; + if (new_capacity > *capacity) { + if (contents) { + new_contents = ts_realloc(contents, new_capacity * element_size); + } else { + new_contents = ts_malloc(new_capacity * element_size); + } + *capacity = new_capacity; + } + return new_contents; +} + +/// This is not what you're looking for, see `array_assign`. +static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity, + const void *other_contents, uint32_t other_size, size_t element_size) { + void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size); + *self_size = other_size; + memcpy(new_contents, other_contents, *self_size * element_size); + return new_contents; +} + +/// This is not what you're looking for, see `array_swap`. +static inline void _array__swap(uint32_t *self_size, uint32_t *self_capacity, + uint32_t *other_size, uint32_t *other_capacity) { + uint32_t tmp_size = *self_size; + uint32_t tmp_capacity = *self_capacity; + *self_size = *other_size; + *self_capacity = *other_capacity; + *other_size = tmp_size; + *other_capacity = tmp_capacity; +} + +/// This is not what you're looking for, see `array_push` or `array_grow_by`. +static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity, + uint32_t count, size_t element_size) { + void *new_contents = contents; + uint32_t new_size = size + count; + if (new_size > *capacity) { + uint32_t new_capacity = *capacity * 2; + if (new_capacity < 8) new_capacity = 8; + if (new_capacity < new_size) new_capacity = new_size; + new_contents = _array__reserve(contents, capacity, element_size, new_capacity); + } + return new_contents; +} + +/// This is not what you're looking for, see `array_splice`. +static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity, + size_t element_size, + uint32_t index, uint32_t old_count, + uint32_t new_count, const void *elements) { + uint32_t new_size = *size + new_count - old_count; + uint32_t old_end = index + old_count; + uint32_t new_end = index + new_count; + assert(old_end <= *size); + + void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size); + + char *contents = (char *)new_contents; + if (*size > old_end) { + memmove( + contents + new_end * element_size, + contents + old_end * element_size, + (*size - old_end) * element_size + ); + } + if (new_count > 0) { + if (elements) { + memcpy( + (contents + index * element_size), + elements, + new_count * element_size + ); + } else { + memset( + (contents + index * element_size), + 0, + new_count * element_size + ); + } + } + *size += new_count - old_count; + + return new_contents; +} + +/// A binary search routine, based on Rust's `std::slice::binary_search_by`. +/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`. +#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \ + do { \ + *(_index) = start; \ + *(_exists) = false; \ + uint32_t size = (self)->size - *(_index); \ + if (size == 0) break; \ + int comparison; \ + while (size > 1) { \ + uint32_t half_size = size / 2; \ + uint32_t mid_index = *(_index) + half_size; \ + comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \ + if (comparison <= 0) *(_index) = mid_index; \ + size -= half_size; \ + } \ + comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \ + if (comparison == 0) *(_exists) = true; \ + else if (comparison < 0) *(_index) += 1; \ + } while (0) + +/// Helper macro for the `_sorted_by` routines below. This takes the left (existing) +/// parameter by reference in order to work with the generic sorting function above. +#define _compare_int(a, b) ((int)*(a) - (int)(b)) + +#ifdef _MSC_VER +#pragma warning(pop) +#elif defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ARRAY_H_ diff --git a/semsitter-sample/src/tree_sitter/parser.h b/semsitter-sample/src/tree_sitter/parser.h new file mode 100644 index 00000000000..858107deb9e --- /dev/null +++ b/semsitter-sample/src/tree_sitter/parser.h @@ -0,0 +1,286 @@ +#ifndef TREE_SITTER_PARSER_H_ +#define TREE_SITTER_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define ts_builtin_sym_error ((TSSymbol)-1) +#define ts_builtin_sym_end 0 +#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 + +#ifndef TREE_SITTER_API_H_ +typedef uint16_t TSStateId; +typedef uint16_t TSSymbol; +typedef uint16_t TSFieldId; +typedef struct TSLanguage TSLanguage; +typedef struct TSLanguageMetadata { + uint8_t major_version; + uint8_t minor_version; + uint8_t patch_version; +} TSLanguageMetadata; +#endif + +typedef struct { + TSFieldId field_id; + uint8_t child_index; + bool inherited; +} TSFieldMapEntry; + +// Used to index the field and supertype maps. +typedef struct { + uint16_t index; + uint16_t length; +} TSMapSlice; + +typedef struct { + bool visible; + bool named; + bool supertype; +} TSSymbolMetadata; + +typedef struct TSLexer TSLexer; + +struct TSLexer { + int32_t lookahead; + TSSymbol result_symbol; + void (*advance)(TSLexer *, bool); + void (*mark_end)(TSLexer *); + uint32_t (*get_column)(TSLexer *); + bool (*is_at_included_range_start)(const TSLexer *); + bool (*eof)(const TSLexer *); + void (*log)(const TSLexer *, const char *, ...); +}; + +typedef enum { + TSParseActionTypeShift, + TSParseActionTypeReduce, + TSParseActionTypeAccept, + TSParseActionTypeRecover, +} TSParseActionType; + +typedef union { + struct { + uint8_t type; + TSStateId state; + bool extra; + bool repetition; + } shift; + struct { + uint8_t type; + uint8_t child_count; + TSSymbol symbol; + int16_t dynamic_precedence; + uint16_t production_id; + } reduce; + uint8_t type; +} TSParseAction; + +typedef struct { + uint16_t lex_state; + uint16_t external_lex_state; +} TSLexMode; + +typedef struct { + uint16_t lex_state; + uint16_t external_lex_state; + uint16_t reserved_word_set_id; +} TSLexerMode; + +typedef union { + TSParseAction action; + struct { + uint8_t count; + bool reusable; + } entry; +} TSParseActionEntry; + +typedef struct { + int32_t start; + int32_t end; +} TSCharacterRange; + +struct TSLanguage { + uint32_t abi_version; + uint32_t symbol_count; + uint32_t alias_count; + uint32_t token_count; + uint32_t external_token_count; + uint32_t state_count; + uint32_t large_state_count; + uint32_t production_id_count; + uint32_t field_count; + uint16_t max_alias_sequence_length; + const uint16_t *parse_table; + const uint16_t *small_parse_table; + const uint32_t *small_parse_table_map; + const TSParseActionEntry *parse_actions; + const char * const *symbol_names; + const char * const *field_names; + const TSMapSlice *field_map_slices; + const TSFieldMapEntry *field_map_entries; + const TSSymbolMetadata *symbol_metadata; + const TSSymbol *public_symbol_map; + const uint16_t *alias_map; + const TSSymbol *alias_sequences; + const TSLexerMode *lex_modes; + bool (*lex_fn)(TSLexer *, TSStateId); + bool (*keyword_lex_fn)(TSLexer *, TSStateId); + TSSymbol keyword_capture_token; + struct { + const bool *states; + const TSSymbol *symbol_map; + void *(*create)(void); + void (*destroy)(void *); + bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); + unsigned (*serialize)(void *, char *); + void (*deserialize)(void *, const char *, unsigned); + } external_scanner; + const TSStateId *primary_state_ids; + const char *name; + const TSSymbol *reserved_words; + uint16_t max_reserved_word_set_size; + uint32_t supertype_count; + const TSSymbol *supertype_symbols; + const TSMapSlice *supertype_map_slices; + const TSSymbol *supertype_map_entries; + TSLanguageMetadata metadata; +}; + +static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) { + uint32_t index = 0; + uint32_t size = len - index; + while (size > 1) { + uint32_t half_size = size / 2; + uint32_t mid_index = index + half_size; + const TSCharacterRange *range = &ranges[mid_index]; + if (lookahead >= range->start && lookahead <= range->end) { + return true; + } else if (lookahead > range->end) { + index = mid_index; + } + size -= half_size; + } + const TSCharacterRange *range = &ranges[index]; + return (lookahead >= range->start && lookahead <= range->end); +} + +/* + * Lexer Macros + */ + +#ifdef _MSC_VER +#define UNUSED __pragma(warning(suppress : 4101)) +#else +#define UNUSED __attribute__((unused)) +#endif + +#define START_LEXER() \ + bool result = false; \ + bool skip = false; \ + UNUSED \ + bool eof = false; \ + int32_t lookahead; \ + goto start; \ + next_state: \ + lexer->advance(lexer, skip); \ + start: \ + skip = false; \ + lookahead = lexer->lookahead; + +#define ADVANCE(state_value) \ + { \ + state = state_value; \ + goto next_state; \ + } + +#define ADVANCE_MAP(...) \ + { \ + static const uint16_t map[] = { __VA_ARGS__ }; \ + for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \ + if (map[i] == lookahead) { \ + state = map[i + 1]; \ + goto next_state; \ + } \ + } \ + } + +#define SKIP(state_value) \ + { \ + skip = true; \ + state = state_value; \ + goto next_state; \ + } + +#define ACCEPT_TOKEN(symbol_value) \ + result = true; \ + lexer->result_symbol = symbol_value; \ + lexer->mark_end(lexer); + +#define END_STATE() return result; + +/* + * Parse Table Macros + */ + +#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT) + +#define STATE(id) id + +#define ACTIONS(id) id + +#define SHIFT(state_value) \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .state = (state_value) \ + } \ + }} + +#define SHIFT_REPEAT(state_value) \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .state = (state_value), \ + .repetition = true \ + } \ + }} + +#define SHIFT_EXTRA() \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .extra = true \ + } \ + }} + +#define REDUCE(symbol_name, children, precedence, prod_id) \ + {{ \ + .reduce = { \ + .type = TSParseActionTypeReduce, \ + .symbol = symbol_name, \ + .child_count = children, \ + .dynamic_precedence = precedence, \ + .production_id = prod_id \ + }, \ + }} + +#define RECOVER() \ + {{ \ + .type = TSParseActionTypeRecover \ + }} + +#define ACCEPT_INPUT() \ + {{ \ + .type = TSParseActionTypeAccept \ + }} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_PARSER_H_ diff --git a/semsitter-sample/tests/jsonl.rs b/semsitter-sample/tests/jsonl.rs new file mode 100644 index 00000000000..4244ec4d083 --- /dev/null +++ b/semsitter-sample/tests/jsonl.rs @@ -0,0 +1,52 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +use std::io::{BufRead, BufReader, Write}; +use std::process::{Command, Stdio}; + +#[test] +fn bridge_speaks_jsonl_on_stdin_stdout() { + let mut child = Command::new(env!("CARGO_BIN_EXE_semsitter-bridge")) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let mut stdin = child.stdin.take().unwrap(); + let mut stdout = BufReader::new(child.stdout.take().unwrap()); + + writeln!(stdin, r#"{{"id":1,"op":"parse","source":"hello"}}"#).unwrap(); + stdin.flush().unwrap(); + let mut line = String::new(); + stdout.read_line(&mut line).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&line).unwrap(); + assert_eq!(parsed["id"], 1); + assert_eq!(parsed["ok"], true); + assert_eq!(parsed["hasError"], false); + assert_eq!(parsed["tree"]["kind"], "source_file"); + assert_eq!(parsed["tree"]["children"][0]["kind"], "hello"); + assert_eq!(parsed["tree"]["startRow"], 0); + + writeln!(stdin, r#"{{"id":2,"op":"quit"}}"#).unwrap(); + stdin.flush().unwrap(); + line.clear(); + stdout.read_line(&mut line).unwrap(); + let quit: serde_json::Value = serde_json::from_str(&line).unwrap(); + assert_eq!(quit["id"], 2); + assert_eq!(quit["ok"], true); + assert!(child.wait().unwrap().success()); +} diff --git a/semsitter-sample/tree-sitter.json b/semsitter-sample/tree-sitter.json new file mode 100644 index 00000000000..8131cca5f38 --- /dev/null +++ b/semsitter-sample/tree-sitter.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json", + "grammars": [ + { + "name": "sample", + "camelcase": "Sample", + "scope": "source.sample", + "path": ".", + "file-types": ["sample"], + "injection-regex": "^sample$" + } + ], + "metadata": { + "version": "0.1.0", + "license": "SSAL", + "description": "Single-rule sample grammar for the semsitter PoC", + "links": { + "repository": "https://github.com/SonarSource/sonar-java" + } + }, + "bindings": { + "c": true, + "go": false, + "node": false, + "python": false, + "rust": true, + "swift": false + } +} diff --git a/sonar-java-plugin/pom.xml b/sonar-java-plugin/pom.xml index af8cd45089c..06467671f5b 100644 --- a/sonar-java-plugin/pom.xml +++ b/sonar-java-plugin/pom.xml @@ -184,8 +184,8 @@ - 21500000 - 19000000 + 13000000 + 10000000 ${project.build.directory}/${project.build.finalName}.jar