diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java index d0e1076e027..2022a9f4bcb 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java @@ -17,14 +17,11 @@ package org.sonar.java.checks.helpers; import com.google.common.io.Files; - import java.io.File; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; - -import org.sonar.java.model.JParser; import org.sonar.java.model.JParserConfig; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.tree.*; @@ -77,7 +74,7 @@ public static CompilationUnitTree parse(File file) { public static CompilationUnitTree parse(String source) { List classpath = Arrays.asList(new File("target/test-classes"), new File("target/classes")); JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), "test", source); + return JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).parse("test", source); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/ClassWithOnlyStaticMethodsInstantiationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ClassWithOnlyStaticMethodsInstantiationCheck.java index f40ea203cde..14ac80c066e 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ClassWithOnlyStaticMethodsInstantiationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ClassWithOnlyStaticMethodsInstantiationCheck.java @@ -16,6 +16,11 @@ */ package org.sonar.java.checks; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; import org.sonar.check.Rule; import org.sonar.java.model.ExpressionUtils; import org.sonar.java.model.JUtils; @@ -30,13 +35,6 @@ import org.sonar.plugins.java.api.tree.Tree.Kind; import org.sonar.plugins.java.api.tree.TypeTree; -import javax.annotation.Nullable; - -import java.text.MessageFormat; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - @Rule(key = "S2440") public class ClassWithOnlyStaticMethodsInstantiationCheck extends IssuableSubscriptionVisitor { diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java index fd512787b13..54710f0f970 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import org.sonar.java.model.JParser; import org.sonar.java.model.JParserConfig; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; @@ -85,7 +84,7 @@ public static CompilationUnitTree parse(File file) { public static CompilationUnitTree parse(String source) { List classpath = Arrays.asList(new File("target/test-classes"), new File("target/classes")); JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), "test", source); + return JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).parse("test", source); } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java index e5c715f9739..d5336417d7d 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java @@ -28,7 +28,6 @@ import org.sonar.api.batch.fs.InputFile; import org.sonar.java.SonarComponents; import org.sonar.java.checks.verifier.TestUtils; -import org.sonar.java.model.JParser; import org.sonar.java.model.JParserConfig; import org.sonar.java.model.VisitorsBridge; import org.sonar.java.model.springcontext.BeanDefinitionGatherer; @@ -174,6 +173,6 @@ private static CompilationUnitTree parse(File file, List classpath) { throw new IllegalStateException("Unable to read file '" + file.getAbsolutePath() + "'", e); } JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), file.getName(), source); + return JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).parse(file.getName(), source); } } diff --git a/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java b/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java index 4e001d09122..fe5752aca7f 100644 --- a/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java +++ b/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java @@ -41,6 +41,7 @@ import org.sonar.java.exceptions.ApiMismatchException; import org.sonar.java.filters.SonarJavaIssueFilter; import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JParserConfig.Result; import org.sonar.java.model.VisitorsBridge; import org.sonar.java.telemetry.Telemetry; import org.sonar.java.telemetry.TelemetryKey; @@ -245,7 +246,7 @@ private void scanBatch(BatchModeContext context, List b analysisProgress.endBatch(); } - private static void scanAsBatchCallback(InputFile inputFile, JParserConfig.Result result, BatchModeContext context, Set environmentsCleaners) { + private static void scanAsBatchCallback(InputFile inputFile, Result result, BatchModeContext context, Set environmentsCleaners) { JavaAstScanner scanner = context.selectScanner(inputFile); Duration duration = PerformanceMeasure.start(context.descriptor(inputFile)); scanner.simpleScan(inputFile, result, ast -> diff --git a/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java b/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java index 250505e02af..bd9ae98082e 100644 --- a/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java +++ b/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java @@ -39,6 +39,7 @@ import org.sonar.java.annotations.VisibleForTesting; import org.sonar.java.model.InputFileUtils; import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JParserConfig.Result; import org.sonar.java.model.JavaTree; import org.sonar.java.model.VisitorsBridge; import org.sonar.java.telemetry.NoOpTelemetry; @@ -150,12 +151,12 @@ private boolean analysisCancelled() { return sonarComponents != null && sonarComponents.analysisCancelled(); } - public void simpleScan(InputFile inputFile, JParserConfig.Result result, Consumer cleanUp) { + public void simpleScan(InputFile inputFile, Result result, Consumer cleanUp) { simpleScan(inputFile, result, cleanUp, compilationUnitTree -> {}); } // modifyCompilationUnit should be used for testing. - public void simpleScan(InputFile inputFile, JParserConfig.Result result, Consumer cleanUp, + public void simpleScan(InputFile inputFile, Result result, Consumer cleanUp, Consumer modifyCompilationUnit) { long startTime = currentTimeMillis(); visitor.setCurrentFile(inputFile); diff --git a/java-frontend/src/main/java/org/sonar/java/model/JInitializerBlockSymbol.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTInitializerBlockSymbol.java similarity index 95% rename from java-frontend/src/main/java/org/sonar/java/model/JInitializerBlockSymbol.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTInitializerBlockSymbol.java index 216ce7413b4..bd86efd96ec 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JInitializerBlockSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTInitializerBlockSymbol.java @@ -14,10 +14,11 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Collections; import java.util.List; +import org.sonar.java.model.Symbols; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; @@ -29,11 +30,11 @@ * defined inside an initializer block can have an owner other than the containing class. This is necessary to properly * detect them as local variables. */ -final class JInitializerBlockSymbol implements Symbol.MethodSymbol { +final class JDTInitializerBlockSymbol implements Symbol.MethodSymbol { private final TypeSymbol owner; private final boolean isStatic; - public JInitializerBlockSymbol(TypeSymbol owner, boolean isStatic) { + public JDTInitializerBlockSymbol(TypeSymbol owner, boolean isStatic) { this.owner = owner; this.isStatic = isStatic; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTMethodSymbol.java similarity index 92% rename from java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTMethodSymbol.java index 3f15b5f9a6b..81b52e09c7c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTMethodSymbol.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.ArrayList; import java.util.Collection; @@ -33,7 +33,7 @@ import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.VariableTree; -final class JMethodSymbol extends JSymbol implements Symbol.MethodSymbol { +final class JDTMethodSymbol extends JDTSymbol implements Symbol.MethodSymbol { /** * Cache for {@link #parameterTypes()}. @@ -59,7 +59,7 @@ final class JMethodSymbol extends JSymbol implements Symbol.MethodSymbol { private final String signature; - JMethodSymbol(JSema sema, IMethodBinding methodBinding) { + JDTMethodSymbol(JDTSema sema, IMethodBinding methodBinding) { super(sema, methodBinding); this.signature = methodBinding().getDeclaringClass().getBinaryName() + "#" + name() @@ -93,7 +93,7 @@ public List declarationParameters() { 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])); + parameters.add(new JDTVariableSymbol.ParameterPlaceholderSymbol(i, sema, methodBinding.getMethodDeclaration(), parameterTypeBindings[i])); } } } @@ -183,6 +183,12 @@ public boolean isParametrizedMethod() { return !isUnknown() && (methodBinding().isParameterizedMethod() || methodBinding().isGenericMethod()); } + @Nullable + @Override + public Object annotationElementDefaultValue() { + return isUnknown() ? null : JDTSymbolMetadata.convertAnnotationValue(sema, methodBinding().getDefaultValue()); + } + @Override public boolean isDefaultMethod() { return !isUnknown() && Modifier.isDefault(binding.getModifiers()); diff --git a/java-frontend/src/main/java/org/sonar/java/jdt/JDTParser.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTParser.java new file mode 100644 index 00000000000..0d5fa3f2165 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTParser.java @@ -0,0 +1,2926 @@ +/* + * 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.jdt; + +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.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.AbstractTypedTree; +import org.sonar.java.model.ArrayDimensionTreeImpl; +import org.sonar.java.model.InternalSyntaxToken; +import org.sonar.java.model.InternalSyntaxTrivia; +import org.sonar.java.model.JLabelSymbol; +import org.sonar.java.model.JParser; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.KeywordSuper; +import org.sonar.java.model.KeywordThis; +import org.sonar.java.model.LineColumnConverter; +import org.sonar.java.model.TypeParameterTreeImpl; +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"}) +final class JDTParser implements JParser { + + private static final Logger LOG = LoggerFactory.getLogger(JDTParser.class); + + private final ASTParser parser; + + JDTParser(ASTParser parser) { + this.parser = parser; + } + + @Override + public JavaTree.CompilationUnitTreeImpl parse(String version, String unitName, String source) { + return parse(parser, version, unitName, source); + } + + /** + * @param unitName see {@link ASTParser#setUnitName(String)} + * @throws RecognitionException in case of syntax errors + */ + 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(astParser, version, unitName, source, astNode); + } + + static JavaTree.CompilationUnitTreeImpl convert(ASTParser astParser, String version, String unitName, String source, CompilationUnit astNode) { + List errors = Stream.of(astNode.getProblems()).filter(IProblem::isError).toList(); + Optional possibleSyntaxError = errors.stream().filter(JDTProblem::isSyntaxError).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); + } + + JDTParser converter = new JDTParser(astParser); + converter.sema = new JDTSema(astNode.getAST()); + converter.compilationUnit = astNode; + converter.tokenManager = createTokenManager(version, unitName, source); + converter.lineColumnConverter = lineColumnConverter; + + JavaTree.CompilationUnitTreeImpl tree = converter.convertCompilationUnit(astNode); + tree.setSemanticModel(converter.sema, JDTProblem.semanticProblems(errors), ASTUtils.getEnvironmentCleaner(astNode.getAST())); + JDTWarning.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 JDTSema 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 = JDTSema.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.addUsage(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.setSymbol(sema.importSymbolOrUnknown(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); + + ITypeBinding binding = e.resolveBinding(); + t.setSymbol(sema.typeSymbolOrUnknown(binding)); + declaration(binding, 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); + + ITypeBinding binding = e.resolveBinding(); + t.setSymbol(sema.typeSymbolOrUnknown(binding)); + declaration(binding, 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) + ); + ITypeBinding binding = e.getAnonymousClassDeclaration().resolveBinding(); + classBody.setSymbol(sema.typeSymbolOrUnknown(binding)); + declaration(binding, 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()); + IMethodBinding constructorBinding; + if (e.getAnonymousClassDeclaration() == null) { + constructorBinding = excludeRecovery(e.resolveConstructorBinding(), arguments.size()); + } else { + constructorBinding = findConstructorForAnonymousClass(e.getAST(), e.getName().resolveTypeBinding(), e.resolveConstructorBinding()); + } + identifier.setSymbol(sema.symbolOrUnknown(constructorBinding)); + usage(constructorBinding, identifier); + + EnumConstantTreeImpl t = new EnumConstantTreeImpl( + convertModifiers(e.modifiers()), + identifier, + new NewClassTreeImpl(identifier, arguments, classBody), + separatorToken + ); + IVariableBinding binding = e.resolveVariable(); + t.setSymbol(sema.variableSymbolOrUnknown(binding)); + declaration(binding, 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())); + + IMethodBinding binding = e.resolveBinding(); + t.setSymbol(sema.methodSymbolOrUnknown(binding)); + declaration(binding, 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(), returnType.resolveBinding()), + createSimpleName(e.getName()), + formalParameters, + throwsToken, + thrownExceptionTypes, + body == null ? null : convertBlock(body), + semcolonToken + ).completeWithModifiers( + convertModifiers(e.modifiers()) + ).completeWithTypeParameters( + convertTypeParameters(e.typeParameters()) + ); + IMethodBinding binding = e.resolveBinding(); + t.setSymbol(sema.methodSymbolOrUnknown(binding)); + declaration(binding, 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(), fieldDeclaration.getType().resolveBinding())); + + 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)); + IVariableBinding binding = fragment.resolveBinding(); + t.setSymbol(sema.variableSymbolOrUnknown(binding)); + declaration(binding, 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.setSymbol(sema.symbolOrUnknown(e.resolveBinding())); + return t; + } + + /** + * @param extraDimensions list of {@link org.eclipse.jdt.core.dom.Dimension} + */ + private TypeTree applyExtraDimensions(TypeTree type, List extraDimensions, @Nullable ITypeBinding 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).setSymbolType(sema.typeOrUnknown(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()); + ITypeBinding typeBinding = e.getType().resolveBinding(); + type = applyExtraDimensions(type, e.extraDimensions(), typeBinding); + if (typeBinding != null && !e.extraDimensions().isEmpty()) { + typeBinding = typeBinding.createArrayType(e.extraDimensions().size()); + } + if (e.isVarargs()) { + type = new JavaTree.ArrayTypeTreeImpl( + type, + (List) convertAnnotations(e.varargsAnnotations()), + firstTokenAfter(e.getType(), TerminalToken.TokenNameELLIPSIS) + ); + if (typeBinding != null) { + ((JavaTree.ArrayTypeTreeImpl) type).setSymbolType(sema.typeOrUnknown(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()) + ); + } + IVariableBinding binding = e.resolveBinding(); + t.setSymbol(sema.variableSymbolOrUnknown(binding)); + if (binding != null) { + if (t.type() instanceof InferedTypeTree inferredType) { + inferredType.setSymbolType(sema.typeOrUnknown(binding.getType())); + } + declaration(binding, 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(), e2.getType().resolveBinding())); + } else { + t.completeTypeAndInitializer( + applyExtraDimensions(type, fragment.extraDimensions(), e2.getType().resolveBinding()), + firstTokenBefore(fragment.getInitializer(), TerminalToken.TokenNameEQUAL), + convertExpression(fragment.getInitializer()) + ); + } + if (i < e2.fragments().size() - 1) { + t.setEndToken(firstTokenAfter(fragment, TerminalToken.TokenNameCOMMA)); + } + IVariableBinding binding = fragment.resolveBinding(); + t.setSymbol(sema.variableSymbolOrUnknown(binding)); + declaration(binding, t); + list.add(t); + } + } + + private VarTypeTreeImpl convertVarType(SimpleType simpleType) { + VarTypeTreeImpl varTree = new VarTypeTreeImpl(firstTokenIn(simpleType.getName(), TerminalToken.TokenNameIdentifier)); + varTree.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(e.resolveTypeBinding())); + t.setSymbol(sema.symbolOrUnknown(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(), e.getType().resolveBinding())) + .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)); + + IVariableBinding binding = fragment.resolveBinding(); + t.setSymbol(sema.variableSymbolOrUnknown(binding)); + declaration(binding, 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), sema.typeOrUnknown(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), sema.typeOrUnknown(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()), + sema.typeOrUnknown(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.setDeclaration(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 + ); + IMethodBinding binding = e.resolveConstructorBinding(); + if (binding != null) { + t.setSymbolType(sema.typeOrUnknown(binding.getDeclaringClass())); + binding = excludeRecovery(binding, arguments.size()); + } + t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); + i.setSymbol(sema.symbolOrUnknown(binding)); + usage(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 + ); + IMethodBinding binding = e.resolveConstructorBinding(); + if (binding != null) { + t.setSymbolType(sema.typeOrUnknown(binding.getDeclaringClass())); + binding = excludeRecovery(binding, arguments.size()); + } + t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); + i.setSymbol(sema.symbolOrUnknown(binding)); + usage(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).setSymbolType(sema.typeOrUnknown(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(e.resolveBinding(), t); + return t; + } + + private MemberSelectExpressionTreeImpl convertQualifiedName(QualifiedName e) { + IdentifierTreeImpl rhs = createSimpleName(e.getName()); + usage(e.getName().resolveBinding(), rhs); + return new MemberSelectExpressionTreeImpl( + convertExpression(e.getQualifier()), + firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), + rhs + ); + } + + private MemberSelectExpressionTreeImpl convertFieldAccess(FieldAccess e) { + IdentifierTreeImpl rhs = createSimpleName(e.getName()); + usage(e.getName().resolveBinding(), rhs); + return new MemberSelectExpressionTreeImpl( + convertExpression(e.getExpression()), + firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT), + rhs + ); + } + + private MemberSelectExpressionTreeImpl convertFieldAccess(SuperFieldAccess e) { + IdentifierTreeImpl rhs = createSimpleName(e.getName()); + usage(e.getName().resolveBinding(), 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()); + ITypeBinding qualifierBinding = e.getQualifier().resolveTypeBinding(); + KeywordSuper keywordSuper = new KeywordSuper(firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamesuper), sema.superSymbolOrUnknown(qualifierBinding)); + MemberSelectExpressionTreeImpl qualifiedSuper = new MemberSelectExpressionTreeImpl( + (ExpressionTree) qualifier, + firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), + keywordSuper + ); + if (qualifierBinding != null) { + qualifiedSuper.setSymbolType(sema.typeOrUnknown(qualifierBinding.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), sema.thisSymbolOrUnknown(e.resolveTypeBinding())); + } + KeywordThis keywordThis = new KeywordThis( + firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamethis), + sema.thisSymbolOrUnknown(e.resolveTypeBinding()) + ); + keywordThis.setSymbolType(sema.typeOrUnknown(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) + ); + ITypeBinding binding = e.getAnonymousClassDeclaration().resolveBinding(); + classBody.setSymbol(sema.typeSymbolOrUnknown(binding)); + declaration(binding, 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(); + IMethodBinding binding; + if (e.getAnonymousClassDeclaration() == null) { + binding = excludeRecovery(e.resolveConstructorBinding(), nbArguments); + } else { + Type constructorType = e.getType() instanceof ParameterizedType parameterizedType ? parameterizedType.getType() : e.getType(); + binding = excludeRecovery(findConstructorForAnonymousClass(e.getAST(), constructorType.resolveBinding(), e.resolveConstructorBinding()), nbArguments); + } + i.setSymbol(sema.symbolOrUnknown(binding)); + usage(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.setSymbolType(sema.typeOrUnknown(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 + ); + IMethodBinding binding = excludeRecovery(e.resolveMethodBinding(), arguments.size()); + t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); + rhs.setSymbol(sema.symbolOrUnknown(binding)); + usage(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()); + ITypeBinding qualifierBinding = e.getQualifier().resolveTypeBinding(); + KeywordSuper keywordSuper = new KeywordSuper(firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamesuper), sema.superSymbolOrUnknown(qualifierBinding)); + MemberSelectExpressionTreeImpl qualifiedSuper = new MemberSelectExpressionTreeImpl( + (ExpressionTree) qualifier, + createSyntaxToken(firstDotTokenIndex), + keywordSuper + ); + if (qualifierBinding != null) { + qualifiedSuper.setSymbolType(sema.typeOrUnknown(qualifierBinding.getSuperclass())); + } + outermostSelect = new MemberSelectExpressionTreeImpl( + qualifiedSuper, + createSyntaxToken(nextTokenIndex(firstDotTokenIndex, TerminalToken.TokenNameDOT)), + rhs + ); + } + + MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( + outermostSelect, + null, + arguments + ); + IMethodBinding binding = excludeRecovery(e.resolveMethodBinding(), arguments.size()); + t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); + rhs.setSymbol(sema.symbolOrUnknown(binding)); + usage(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 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.setSymbol(sema.variableSymbolOrUnknown(variableBinding)); + ((InferedTypeTree) t.type()).setSymbolType(sema.typeOrUnknown(variableBinding.getType())); + declaration(variableBinding, t); + } + } 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.setSymbol(sema.methodSymbolOrUnknown(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)); + IMethodBinding binding = e.resolveMethodBinding(); + i.setSymbol(sema.symbolOrUnknown(binding)); + usage(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(e.getName().resolveBinding(), 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(e.getName().resolveBinding(), 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(e.getName().resolveBinding(), 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).setSymbolType(sema.typeOrUnknown(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, sema.superSymbolOrUnknown(abstractTypeDeclaration.resolveBinding())); + } + if (node instanceof AnonymousClassDeclaration anonymousClassDeclaration) { + return new KeywordSuper(token, sema.superSymbolOrUnknown(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.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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).setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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)); + } + 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)); + } + +} diff --git a/java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java new file mode 100644 index 00000000000..b4660a45ee9 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java @@ -0,0 +1,259 @@ +/* + * 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.jdt; + +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.annotations.VisibleForTesting; +import org.sonar.java.model.JParser; +import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JavaTree; +import org.sonar.plugins.java.api.JavaVersion; +import org.sonarsource.analyzer.commons.ProgressReport; +import org.sonarsource.performance.measure.PerformanceMeasure; + +public abstract class JDTParserConfig implements JParserConfig { + + private static final Logger LOG = LoggerFactory.getLogger(JDTParserConfig.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; + + private JDTParserConfig(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { + this.javaVersion = javaVersion; + this.classpath = classpath; + this.shouldIgnoreUnnamedModuleForSplitPackage = shouldIgnoreUnnamedModuleForSplitPackage; + } + + public static JParserConfig create(JParserConfig.Mode mode, JavaVersion javaVersion, List classpath, + boolean shouldIgnoreUnnamedModuleForSplitPackage) { + if (shouldIgnoreUnnamedModuleForSplitPackage) { + LOG.info("The Java analyzer will ignore the unnamed module for split packages."); + } + return switch (mode) { + case BATCH -> new Batch(javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); + case FILE_BY_FILE -> new FileByFile(javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); + }; + } + + @Override + public JavaTree.CompilationUnitTreeImpl parse(String unitName, String source) { + return astParser().parse(javaVersion.effectiveJavaVersionAsString(), unitName, source); + } + + @Override + public JParser astParser() { + return new JDTParser(jdtParser()); + } + + ASTParser jdtParser() { + 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 + JDTProblem.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 JDTParserConfig { + + Batch(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { + super(javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); + } + + @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 astParser = jdtParser(); + 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 = Result.success(JDTParser.convert(astParser, javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents(), ast)); + } catch (Exception e) { + result = Result.failure(e); + } + convertDuration.stop(); + PerformanceMeasure.Duration analyzeDuration = PerformanceMeasure.start("Analyze"); + action.accept(inputFile, result); + + notYetAnalyzedFiles.remove(inputFile); + executionTimeReport.end(); + analyzeDuration.stop(); + } + }, 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); + } + } 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(jdtParser(), inputFile, javaVersion, action); + } + } + + } + + private static class FileByFile extends JDTParserConfig { + + private FileByFile(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { + super(javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); + } + + @Override + public void parse(Iterable inputFiles, BooleanSupplier isCanceled, + AnalysisProgress analysisProgress, BiConsumer action) { + boolean successfullyCompleted = false; + boolean cancelled = false; + + ExecutionTimeReport executionTimeReport = new ExecutionTimeReport(); + ProgressReport progressReport = new ProgressReport("Report about progress of Java AST analyzer", TimeUnit.SECONDS.toMillis(10)); + List filesNames = StreamSupport.stream(inputFiles.spliterator(), false) + .map(InputFile::toString) + .toList(); + progressReport.start(filesNames); + try { + for (InputFile inputFile : inputFiles) { + if (isCanceled.getAsBoolean()) { + cancelled = true; + break; + } + executionTimeReport.start(inputFile); + parse(jdtParser(), inputFile, javaVersion, action); + executionTimeReport.end(); + progressReport.nextFile(); + } + successfullyCompleted = !cancelled; + } finally { + if (successfullyCompleted) { + progressReport.stop(); + } else { + progressReport.cancel(); + } + executionTimeReport.report(); + } + } + + private static void parse(ASTParser astParser, InputFile inputFile, JavaVersion javaVersion, BiConsumer action) { + Result result; + PerformanceMeasure.Duration parseDuration = PerformanceMeasure.start("JParser"); + try { + result = Result.success(JDTParser.parse(astParser, javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents())); + } catch (Exception e) { + result = Result.failure(e); + } finally { + parseDuration.stop(); + } + action.accept(inputFile, result); + } + } + + @VisibleForTesting + static boolean shouldEnablePreviewFlag(JavaVersion currentVersion) { + return currentVersion.arePreviewFeaturesEnabled(); + } + +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/JProblem.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTProblem.java similarity index 91% rename from java-frontend/src/main/java/org/sonar/java/model/JProblem.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTProblem.java index 2dc6186f0b9..59995e11cf5 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JProblem.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTProblem.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.List; import java.util.Map; @@ -27,7 +27,7 @@ import org.sonar.plugins.java.api.diagnostics.Problem; import org.sonar.plugins.java.api.diagnostics.ProblemKind; -public class JProblem implements Problem { +class JDTProblem implements Problem { private static final String USE_ENABLE_PREVIEW = " Use --enable-preview to enable"; @@ -52,7 +52,7 @@ public class JProblem implements Problem { private final String message; private final ProblemKind type; - JProblem(String message, ProblemKind type) { + JDTProblem(String message, ProblemKind type) { this.message = type == ProblemKind.PREVIEW_FEATURE_USED && message.endsWith(USE_ENABLE_PREVIEW) ? message.substring(0, message.length() - USE_ENABLE_PREVIEW.length()) : message; @@ -79,7 +79,7 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof JProblem that)) { + if (!(o instanceof JDTProblem that)) { return false; } return type == that.type && message.equals(that.message); @@ -109,7 +109,7 @@ static boolean isSyntaxError(IProblem problem) { static Set semanticProblems(List errors) { return errors.stream() .filter(error -> (error.getID() & IProblem.UndefinedType) != 0) - .map(error -> new JProblem(error.getMessage(), (error.getID() & IProblem.PreviewFeatureUsed) == 0 ? ProblemKind.UNDEFINED_TYPE : ProblemKind.PREVIEW_FEATURE_USED)) + .map(error -> new JDTProblem(error.getMessage(), (error.getID() & IProblem.PreviewFeatureUsed) == 0 ? ProblemKind.UNDEFINED_TYPE : ProblemKind.PREVIEW_FEATURE_USED)) .collect(Collectors.toSet()); } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSema.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTSema.java similarity index 71% rename from java-frontend/src/main/java/org/sonar/java/model/JSema.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTSema.java index 573ac8400cc..d928c9a3071 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSema.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTSema.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Arrays; import java.util.Collections; @@ -36,19 +36,19 @@ import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.Tree; -public final class JSema implements Sema { +final class JDTSema implements Sema { private final AST ast; 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 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) { + JDTSema(AST ast) { this.ast = ast; } @@ -97,12 +97,12 @@ Symbol superSymbolOrUnknown(@Nullable ITypeBinding binding) { return binding == null ? Symbol.UNKNOWN_SYMBOL : typeSymbol(binding).superSymbol; } - public JType type(ITypeBinding typeBinding) { - JType cached = types.get(typeBinding); + public JDTType type(ITypeBinding typeBinding) { + JDTType cached = types.get(typeBinding); if (cached != null) { return cached; } - return types.computeIfAbsent(typeBinding, k -> new JType(this, JType.normalize(k))); + return types.computeIfAbsent(typeBinding, k -> new JDTType(this, JDTType.normalize(k))); } List types(ITypeBinding[] typeBindings) { @@ -120,59 +120,59 @@ public Symbol packageSymbol(@Nullable IPackageBinding packageBinding) { if (packageBinding == null) { return Symbol.ROOT_PACKAGE; } - JSymbol cached = symbols.get(packageBinding); + JDTSymbol cached = symbols.get(packageBinding); if (cached != null) { return cached; } - return symbols.computeIfAbsent(packageBinding, k -> new JPackageSymbol(this, (IPackageBinding) k)); + return symbols.computeIfAbsent(packageBinding, k -> new JDTSymbol(this, k)); } - public JTypeSymbol typeSymbol(ITypeBinding typeBinding) { - JTypeSymbol cached = (JTypeSymbol) symbols.get(typeBinding); + public JDTTypeSymbol typeSymbol(ITypeBinding typeBinding) { + JDTTypeSymbol cached = (JDTTypeSymbol) symbols.get(typeBinding); if (cached != null) { return cached; } - return (JTypeSymbol) symbols.computeIfAbsent(typeBinding, k -> new JTypeSymbol(this, JType.normalize((ITypeBinding) k))); + return (JDTTypeSymbol) symbols.computeIfAbsent(typeBinding, k -> new JDTTypeSymbol(this, JDTType.normalize((ITypeBinding) k))); } - public JMethodSymbol methodSymbol(IMethodBinding methodBinding) { - JMethodSymbol cached = (JMethodSymbol) symbols.get(methodBinding); + public JDTMethodSymbol methodSymbol(IMethodBinding methodBinding) { + JDTMethodSymbol cached = (JDTMethodSymbol) symbols.get(methodBinding); if (cached != null) { return cached; } - return (JMethodSymbol) symbols.computeIfAbsent(methodBinding, k -> new JMethodSymbol(this, (IMethodBinding) k)); + return (JDTMethodSymbol) symbols.computeIfAbsent(methodBinding, k -> new JDTMethodSymbol(this, (IMethodBinding) k)); } - public JInitializerBlockSymbol initializerBlockSymbol(JTypeSymbol owner) { - JInitializerBlockSymbol cached = initializerBlockSymbols.get(owner); + public JDTInitializerBlockSymbol initializerBlockSymbol(JDTTypeSymbol owner) { + JDTInitializerBlockSymbol cached = initializerBlockSymbols.get(owner); if (cached != null) { return cached; } - return initializerBlockSymbols.computeIfAbsent(owner, k -> new JInitializerBlockSymbol(k, false)); + return initializerBlockSymbols.computeIfAbsent(owner, k -> new JDTInitializerBlockSymbol(k, false)); } - public JInitializerBlockSymbol staticInitializerBlockSymbol(JTypeSymbol owner) { - JInitializerBlockSymbol cached = staticInitializerBlockSymbols.get(owner); + public JDTInitializerBlockSymbol staticInitializerBlockSymbol(JDTTypeSymbol owner) { + JDTInitializerBlockSymbol cached = staticInitializerBlockSymbols.get(owner); if (cached != null) { return cached; } - return staticInitializerBlockSymbols.computeIfAbsent(owner, k -> new JInitializerBlockSymbol(k, true)); + return staticInitializerBlockSymbols.computeIfAbsent(owner, k -> new JDTInitializerBlockSymbol(k, true)); } - public JVariableSymbol variableSymbol(IVariableBinding variableBinding) { - JVariableSymbol cached = (JVariableSymbol) symbols.get(variableBinding); + public JDTVariableSymbol variableSymbol(IVariableBinding variableBinding) { + JDTVariableSymbol cached = (JDTVariableSymbol) symbols.get(variableBinding); if (cached != null) { return cached; } - return (JVariableSymbol) symbols.computeIfAbsent(variableBinding, k -> new JVariableSymbol(this, (IVariableBinding) k)); + return (JDTVariableSymbol) symbols.computeIfAbsent(variableBinding, k -> new JDTVariableSymbol(this, (IVariableBinding) k)); } - JSymbolMetadata.JAnnotationInstance annotation(IAnnotationBinding annotationBinding) { - JSymbolMetadata.JAnnotationInstance cached = annotations.get(annotationBinding); + JDTSymbolMetadata.JAnnotationInstance annotation(IAnnotationBinding annotationBinding) { + JDTSymbolMetadata.JAnnotationInstance cached = annotations.get(annotationBinding); if (cached != null) { return cached; } - return annotations.computeIfAbsent(annotationBinding, k -> new JSymbolMetadata.JAnnotationInstance(this, k)); + return annotations.computeIfAbsent(annotationBinding, k -> new JDTSymbolMetadata.JAnnotationInstance(this, k)); } static IBinding declarationBinding(IBinding binding) { @@ -224,8 +224,4 @@ IAnnotationBinding[] resolvePackageAnnotations(String packageName) { return ASTUtils.resolvePackageAnnotations(ast, packageName); } - public Runnable getEnvironmentCleaner() { - return ASTUtils.getEnvironmentCleaner(ast); - } - } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTSymbol.java similarity index 92% rename from java-frontend/src/main/java/org/sonar/java/model/JSymbol.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTSymbol.java index d63bd1380e7..d0f272cd6a6 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTSymbol.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Arrays; import java.util.Collections; @@ -26,6 +26,7 @@ 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.Symbols; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; @@ -33,9 +34,9 @@ import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.Tree; -abstract class JSymbol implements Symbol { +class JDTSymbol implements Symbol { - protected final JSema sema; + protected final JDTSema sema; protected final IBinding binding; /** @@ -53,7 +54,7 @@ abstract class JSymbol implements Symbol { */ private SymbolMetadata metadata; - JSymbol(JSema sema, IBinding binding) { + JDTSymbol(JDTSema sema, IBinding binding) { this.sema = Objects.requireNonNull(sema); this.binding = Objects.requireNonNull(binding); } @@ -63,16 +64,16 @@ public final boolean equals(Object obj) { if (this == obj) { return true; } - if (!(obj instanceof JSymbol)) { + if (!(obj instanceof JDTSymbol)) { return false; } - JSymbol other = (JSymbol) obj; + JDTSymbol other = (JDTSymbol) obj; if (this.binding.getKind() != other.binding.getKind()) { return false; } switch (this.binding.getKind()) { case IBinding.TYPE: - return JType.areEqual( + return JDTType.areEqual( (ITypeBinding) this.binding, (ITypeBinding) other.binding ); @@ -82,13 +83,13 @@ public final boolean equals(Object obj) { other ); case IBinding.METHOD: - return areEqualMethodSymbols((JMethodSymbol) this, (JMethodSymbol) other); + return areEqualMethodSymbols((JDTMethodSymbol) this, (JDTMethodSymbol) other); default: return super.equals(obj); } } - private static boolean areEqualVariables(JSymbol thisVariableSymbol, JSymbol otherVariableSymbol) { + private static boolean areEqualVariables(JDTSymbol thisVariableSymbol, JDTSymbol otherVariableSymbol) { IVariableBinding thisBinding = (IVariableBinding) thisVariableSymbol.binding; IVariableBinding otherBinding = (IVariableBinding) otherVariableSymbol.binding; return thisBinding.getVariableId() == otherBinding.getVariableId() @@ -106,7 +107,7 @@ private static boolean areEqualVariables(JSymbol thisVariableSymbol, JSymbol oth * 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) { + private static boolean areEqualMethodSymbols(JDTMethodSymbol thisMethodSymbol, JDTMethodSymbol 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. @@ -132,7 +133,7 @@ public final int hashCode() { } /** - * @see JType#name() + * @see JDTType#name() */ @Override public final String name() { @@ -230,7 +231,7 @@ private Symbol ownerOfRecordComponentConstant(IVariableBinding variableBinding) case CLASS, RECORD, ENUM: - JTypeSymbol typeSymbol = (JTypeSymbol) ((ClassTreeImpl) node).symbol(); + JDTTypeSymbol typeSymbol = (JDTTypeSymbol) ((ClassTreeImpl) node).symbol(); if (initializerBlock) { return sema.initializerBlockSymbol(typeSymbol); } @@ -378,11 +379,11 @@ public final SymbolMetadata metadata() { private SymbolMetadata convertMetadata() { switch (binding.getKind()) { case IBinding.PACKAGE: - return new JSymbolMetadata(sema, this, sema.resolvePackageAnnotations(binding.getName())); + return new JDTSymbolMetadata(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 new JDTSymbolMetadata(sema, this, binding.getAnnotations()); } return convertMetadata(type); case IBinding.METHOD: @@ -393,12 +394,12 @@ private SymbolMetadata convertMetadata() { } return convertMetadata(returnType); default: - return new JSymbolMetadata(sema, this, binding.getAnnotations()); + return new JDTSymbolMetadata(sema, this, binding.getAnnotations()); } } private SymbolMetadata convertMetadata(ITypeBinding type) { - return JSymbolMetadata.of(sema, this, type, binding.getAnnotations()); + return JDTSymbolMetadata.of(sema, this, type, binding.getAnnotations()); } /** @@ -470,14 +471,14 @@ private TypeSymbol variableEnclosingClass(IVariableBinding variableBinding) { @Override public final List usages() { - List usages = sema.usages.get(JSema.declarationBinding(binding)); + List usages = sema.usages.get(JDTSema.declarationBinding(binding)); return usages != null ? usages : Collections.emptyList(); } @Nullable @Override public Tree declaration() { - return sema.declarations.get(JSema.declarationBinding(binding)); + return sema.declarations.get(JDTSema.declarationBinding(binding)); } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTSymbolMetadata.java similarity index 67% rename from java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTSymbolMetadata.java index 2b9c5b6b862..f5fe9747d94 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTSymbolMetadata.java @@ -14,21 +14,21 @@ * 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.model; +package org.sonar.java.jdt; 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.java.model.AnnotationValueImpl; +import org.sonar.java.model.ModifiersUtils; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.tree.AnnotationTree; @@ -36,16 +36,12 @@ import org.sonar.plugins.java.api.tree.Tree; import static org.sonar.java.model.JSymbolMetadataNullabilityHelper.getNullabilityDataAtLevel; +import static org.sonar.java.model.JNullabilityData.noNullabilityAnnotationAt; +import static org.sonar.java.model.JNullabilityData.unknownNullabilityAt; -final class JSymbolMetadata implements SymbolMetadata { +final class JDTSymbolMetadata implements SymbolMetadata { - private static final NullabilityData[] NO_ANNOTATION_NULLABILITY = - forEachLevel(level -> new JNullabilityData(NullabilityType.NO_ANNOTATION, level, null, null, false)); - - private static final NullabilityData[] UNKNOWN_NULLABILITY = - forEachLevel(level -> new JNullabilityData(NullabilityType.UNKNOWN, level, null, null, false)); - - private final JSema sema; + private final JDTSema sema; private final Symbol symbol; private final IAnnotationBinding[] symbolBindings; private final SymbolMetadata[] parameterMetas; @@ -65,7 +61,7 @@ final class JSymbolMetadata implements SymbolMetadata { private final boolean isTypeArgMetadata; - JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] annotationBindings) { + JDTSymbolMetadata(JDTSema sema, Symbol symbol, IAnnotationBinding[] annotationBindings) { this.sema = Objects.requireNonNull(sema); this.symbol = symbol; this.symbolBindings = annotationBindings; @@ -75,7 +71,7 @@ final class JSymbolMetadata implements SymbolMetadata { this.annotationBindings = annotationBindings; } - private JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] symbolAnnotations, JSymbolMetadata[] parameterMetaDatas, boolean isTypeArgMetadata) { + private JDTSymbolMetadata(JDTSema sema, Symbol symbol, IAnnotationBinding[] symbolAnnotations, JDTSymbolMetadata[] parameterMetaDatas, boolean isTypeArgMetadata) { this.sema = Objects.requireNonNull(sema); this.symbol = symbol; this.symbolBindings = symbolAnnotations; @@ -83,7 +79,7 @@ private JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] symbolAn this.isTypeArgMetadata = isTypeArgMetadata; int total = symbolBindings.length; - for (JSymbolMetadata parameterMetaData : parameterMetaDatas) { + for (JDTSymbolMetadata parameterMetaData : parameterMetaDatas) { total += parameterMetaData.annotationBindings.length; } @@ -91,43 +87,31 @@ private JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] symbolAn int current = symbolAnnotations.length; System.arraycopy(symbolAnnotations, 0, this.annotationBindings, 0, symbolAnnotations.length); - for (JSymbolMetadata parameterMetaData : parameterMetaDatas) { + for (JDTSymbolMetadata 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) { + public static JDTSymbolMetadata of(JDTSema 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); + .toArray(JDTSymbolMetadata[]::new); - return new JSymbolMetadata(sema, owner, symbolAnnotations, parameterMetaDatas, false); + return new JDTSymbolMetadata(sema, owner, symbolAnnotations, parameterMetaDatas, false); } - private static JSymbolMetadata ofTypeArg(JSema sema, Symbol owner, ITypeBinding param) { + private static JDTSymbolMetadata ofTypeArg(JDTSema 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 static NullabilityData[] forEachLevel(Function initializer) { - return Arrays.stream(NullabilityLevel.values()).map(initializer).toArray(NullabilityData[]::new); - } - - public static NullabilityData noNullabilityAnnotationAt(NullabilityLevel level) { - return NO_ANNOTATION_NULLABILITY[level.ordinal()]; - } - - public static NullabilityData unknownNullabilityAt(NullabilityLevel level) { - return UNKNOWN_NULLABILITY[level.ordinal()]; + .toArray(JDTSymbolMetadata[]::new); + return new JDTSymbolMetadata(sema, owner, symbolAnnotations, parameterMetaDatas, true); } @Override @@ -231,7 +215,7 @@ private NullabilityData resolveNullability(NullabilityTarget target) { // Not annotated or meta annotated, check upper level in the ownership hierarchy... if (symbol.isPackageSymbol()) { - return NO_ANNOTATION_NULLABILITY[currentLevel.ordinal()]; + return noNullabilityAnnotationAt(currentLevel); } Symbol owner = getEffectiveOwner(symbol, currentLevel); if (owner == null) return unknownNullabilityAt(currentLevel); @@ -249,7 +233,7 @@ private static NullabilityData getNullabilityDataFromInheritance(Symbol.MethodSy return nullabilityData; } } - return NO_ANNOTATION_NULLABILITY[level.ordinal()]; + return noNullabilityAnnotationAt(level); } @CheckForNull @@ -302,84 +286,8 @@ private static NullabilityTarget getTarget(Symbol symbol) { return null; } - static final class JNullabilityData implements NullabilityData { - - private final NullabilityType type; - private final NullabilityLevel level; - - @Nullable - private final AnnotationInstance annotation; - @Nullable - private final AnnotationTree annotationTree; - - private final boolean metaAnnotation; - - public JNullabilityData(NullabilityType type, NullabilityLevel level, @Nullable AnnotationInstance annotation, - @Nullable AnnotationTree annotationTree, boolean metaAnnotation) { - this.type = type; - this.level = level; - this.annotation = annotation; - this.annotationTree = annotationTree; - this.metaAnnotation = metaAnnotation; - } - - @Override - public NullabilityType type() { - return type; - } - - @Override - public NullabilityLevel level() { - return level; - } - - @Override - public boolean metaAnnotation() { - return metaAnnotation; - } - - @Override - public boolean isNonNull(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { - return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, t -> t == NullabilityType.NON_NULL); - } - - @Override - public boolean isNullable(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { - return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, - t -> t == NullabilityType.STRONG_NULLABLE || t == NullabilityType.WEAK_NULLABLE); - } - - @Override - public boolean isStrongNullable(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { - return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, t -> t == NullabilityType.STRONG_NULLABLE); - } - - private boolean testNullabilityType(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue, Predicate typePredicate) { - if (type == NullabilityType.NO_ANNOTATION) { - return false; - } else if (type == NullabilityType.UNKNOWN || (ignoreMetaAnnotation && metaAnnotation)) { - return defaultValue; - } else if (typePredicate.test(type)) { - return minLevel.ordinal() <= level.ordinal(); - } - return false; - } - - @Nullable - @Override - public AnnotationInstance annotation() { - return annotation; - } - - @Nullable - @Override - public Tree declaration() { - return annotationTree; - } - } - static final class JAnnotationInstance implements AnnotationInstance { - private final JSema sema; + private final JDTSema sema; private final IAnnotationBinding annotationBinding; /** @@ -387,7 +295,7 @@ static final class JAnnotationInstance implements AnnotationInstance { */ private List values; - JAnnotationInstance(JSema sema, IAnnotationBinding annotationBinding) { + JAnnotationInstance(JDTSema sema, IAnnotationBinding annotationBinding) { this.sema = sema; this.annotationBinding = annotationBinding; } @@ -401,27 +309,26 @@ public Symbol symbol() { public List values() { if (values == null) { values = Arrays.stream(annotationBinding.getDeclaredMemberValuePairs()) - .map(p -> new AnnotationValueImpl(p.getName(), convertAnnotationValue(p.getValue()))) + .map(p -> new AnnotationValueImpl(p.getName(), convertAnnotationValue(sema, 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; + static Object convertAnnotationValue(JDTSema sema, 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(sema, a[i]); } - case null, default -> value; - }; - } + 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/jdt/JDTType.java similarity index 95% rename from java-frontend/src/main/java/org/sonar/java/model/JType.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTType.java index ee13c72eebe..fbb6e3f603f 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JType.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTType.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Arrays; import java.util.List; @@ -25,14 +25,15 @@ import org.eclipse.jdt.core.dom.ITypeBinding; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonar.java.model.JUtils; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; -final class JType implements Type, Type.ArrayType { +final class JDTType implements Type.ArrayType { - private static final Logger LOG = LoggerFactory.getLogger(JType.class); + private static final Logger LOG = LoggerFactory.getLogger(JDTType.class); - final JSema sema; + final JDTSema sema; final ITypeBinding typeBinding; private final String fullyQualifiedName; @@ -57,7 +58,7 @@ final class JType implements Type, Type.ArrayType { */ private List typeArguments; - JType(JSema sema, ITypeBinding typeBinding) { + JDTType(JDTSema sema, ITypeBinding typeBinding) { this.sema = Objects.requireNonNull(sema); this.typeBinding = Objects.requireNonNull(typeBinding); this.fullyQualifiedName = fullyQualifiedName(typeBinding); @@ -76,7 +77,7 @@ public boolean isSubtypeOf(String fullyQualifiedName) { @Override public boolean isSubtypeOf(Type superType) { return !superType.isUnknown() - && isSubtype(this.typeBinding, ((JType) superType).typeBinding); + && isSubtype(this.typeBinding, ((JDTType) superType).typeBinding); } private static boolean isSubtype(ITypeBinding left, ITypeBinding right) { @@ -267,7 +268,7 @@ public Type[] getIntersectionTypes() { } /** - * @see JSymbol#name() + * @see JDTSymbol#name() */ @Override public String name() { @@ -305,7 +306,7 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (obj instanceof JType other) { + if (obj instanceof JDTType other) { return areEqual(this.typeBinding, other.typeBinding); } return false; diff --git a/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTTypeSymbol.java similarity index 87% rename from java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTTypeSymbol.java index 0d41214e309..68b5e4408ae 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTTypeSymbol.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.ArrayList; import java.util.Arrays; @@ -29,13 +29,14 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.sonar.java.model.Symbols; 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 { +final class JDTTypeSymbol extends JDTSymbol implements Symbol.TypeSymbol { /** * Cache for {@link #superClass()}. @@ -68,9 +69,9 @@ 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(); + return JDTTypeSymbol.this.type(); } - Type superClass = JTypeSymbol.this.superClass(); + Type superClass = JDTTypeSymbol.this.superClass(); return superClass == null ? Type.UNKNOWN : superClass; } }; @@ -83,11 +84,11 @@ public String name() { @Override public Type type() { - return JTypeSymbol.this.type(); + return JDTTypeSymbol.this.type(); } }; - JTypeSymbol(JSema sema, ITypeBinding typeBinding) { + JDTTypeSymbol(JDTSema sema, ITypeBinding typeBinding) { super(sema, typeBinding); } @@ -162,12 +163,24 @@ public Set superTypes() { superTypes = Collections.emptySet(); } else { superTypes = new HashSet<>(); - JUtils.collectSuperTypes(superTypes, sema, typeBinding()); + collectSuperTypes(superTypes, typeBinding()); } } return superTypes; } + private void collectSuperTypes(Set result, ITypeBinding typeBinding) { + ITypeBinding s = typeBinding.getSuperclass(); + if (s != null) { + result.add(sema.type(s)); + collectSuperTypes(result, s); + } + Arrays.stream(typeBinding.getInterfaces()).forEach(i -> { + result.add(sema.type(i)); + collectSuperTypes(result, i); + }); + } + @Override @Nullable public TypeSymbol outermostClass() { @@ -188,7 +201,7 @@ public boolean isAnnotation() { abstract class SpecialField extends Symbols.DefaultSymbol implements Symbol.VariableSymbol { @Override public final Symbol owner() { - return JTypeSymbol.this; + return JDTTypeSymbol.this; } @Override @@ -208,7 +221,7 @@ public final boolean isUnknown() { @Override public final TypeSymbol enclosingClass() { - return JTypeSymbol.this; + return JDTTypeSymbol.this; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTVariableSymbol.java similarity index 90% rename from java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTVariableSymbol.java index ae6458f39de..d2b35e272af 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTVariableSymbol.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Collections; import java.util.List; @@ -23,19 +23,20 @@ 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.model.Symbols; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.VariableTree; -final class JVariableSymbol extends JSymbol implements Symbol.VariableSymbol { +final class JDTVariableSymbol extends JDTSymbol implements Symbol.VariableSymbol { // cache for this.constantValue() private boolean constantValueComputed = false; private Optional constantValue; - JVariableSymbol(JSema sema, IVariableBinding variableBinding) { + JDTVariableSymbol(JDTSema sema, IVariableBinding variableBinding) { super(sema, variableBinding); } @@ -89,11 +90,11 @@ static class ParameterPlaceholderSymbol extends Symbols.DefaultSymbol implements private final Type type; private final SymbolMetadata metadata; - ParameterPlaceholderSymbol(int index, JSema sema, IMethodBinding owner, ITypeBinding typeBinding) { + ParameterPlaceholderSymbol(int index, JDTSema sema, IMethodBinding owner, ITypeBinding typeBinding) { this.name = "arg" + index; this.owner = sema.methodSymbol(owner); this.type = sema.type(typeBinding); - this.metadata = JSymbolMetadata.of(sema, this, typeBinding, owner.getParameterAnnotations(index)); + this.metadata = JDTSymbolMetadata.of(sema, this, typeBinding, owner.getParameterAnnotations(index)); } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/JWarning.java b/java-frontend/src/main/java/org/sonar/java/jdt/JDTWarning.java similarity index 83% rename from java-frontend/src/main/java/org/sonar/java/model/JWarning.java rename to java-frontend/src/main/java/org/sonar/java/jdt/JDTWarning.java index 6a9734c38cc..da9f1d4107f 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JWarning.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTWarning.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.ArrayList; import java.util.Comparator; @@ -33,6 +33,9 @@ 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.ExpressionUtils; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.LineColumnConverter; import org.sonar.java.model.location.InternalPosition; import org.sonar.plugins.java.api.diagnostics.ProblemKind; import org.sonar.plugins.java.api.diagnostics.Warning; @@ -43,13 +46,13 @@ import static org.sonar.plugins.java.api.location.Position.endOf; import static org.sonar.plugins.java.api.location.Position.startOf; -public final class JWarning extends JProblem implements Warning { +final class JDTWarning extends JDTProblem implements Warning { private final Position start; private final Position end; private Tree syntaxTree; - JWarning(String message, ProblemKind type, int startLine, int startColumnOffset, int endLine, int endColumnOffset) { + JDTWarning(String message, ProblemKind type, int startLine, int startColumnOffset, int endLine, int endColumnOffset) { super(message, type); this.start = InternalPosition.atOffset(startLine, startColumnOffset); this.end = InternalPosition.atOffset(endLine, endColumnOffset); @@ -73,13 +76,13 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof JWarning)) { + if (!(o instanceof JDTWarning)) { return false; } if (!super.equals(o)) { return false; } - JWarning jWarning = (JWarning) o; + JDTWarning jWarning = (JDTWarning) o; // skip syntaxTree return start.equals(jWarning.start) && end.equals(jWarning.end); } @@ -102,9 +105,9 @@ public static class Mapper extends SubscriptionVisitor { .flatMap(Set::stream) .collect(Collectors.toSet()); - private final Map> warningsByType = new EnumMap<>(ProblemKind.class); + private final Map> warningsByType = new EnumMap<>(ProblemKind.class); - private final PriorityQueue warnings = new PriorityQueue<>(Comparator.comparing(JWarning::start).thenComparing(JWarning::end)); + private final PriorityQueue warnings = new PriorityQueue<>(Comparator.comparing(JDTWarning::start).thenComparing(JDTWarning::end)); private Mapper(CompilationUnit ast, LineColumnConverter lineColumnConverter) { WARNING_KINDS.keySet().forEach(kind -> warningsByType.put(kind, new LinkedHashSet<>())); @@ -116,14 +119,14 @@ private Mapper(CompilationUnit ast, LineColumnConverter lineColumnConverter) { @CheckForNull @VisibleForTesting - static JWarning convert(IProblem problem, LineColumnConverter lineColumnConverter) { - ProblemKind kind = JProblem.kindOf(problem); + static JDTWarning convert(IProblem problem, LineColumnConverter lineColumnConverter) { + ProblemKind kind = JDTProblem.kindOf(problem); if (kind == null || !WARNING_KINDS.containsKey(kind)) { return null; } LineColumnConverter.Pos start = lineColumnConverter.toPos(problem.getSourceStart()); LineColumnConverter.Pos end = lineColumnConverter.toPos(problem.getSourceEnd()); - return new JWarning(problem.getMessage(), kind, start.line(), start.columnOffset(), end.line(), end.columnOffset()); + return new JDTWarning(problem.getMessage(), kind, start.line(), start.columnOffset(), end.line(), end.columnOffset()); } public static Mapper warningsFor(CompilationUnit ast, LineColumnConverter lineColumnConverter) { @@ -157,8 +160,8 @@ public void visitNode(Tree tree) { if (warnings.isEmpty()) { return; } - for (Iterator iterator = warnings.iterator(); iterator.hasNext();) { - JWarning warning = iterator.next(); + for (Iterator iterator = warnings.iterator(); iterator.hasNext();) { + JDTWarning warning = iterator.next(); if (isInsideTree(warning, tree)) { setSyntaxTree(warning, tree); warningsByType.computeIfAbsent(warning.type(), k -> new LinkedHashSet<>()).add(warning); @@ -171,14 +174,14 @@ public void visitNode(Tree tree) { } @VisibleForTesting - static void setSyntaxTree(JWarning warning, Tree tree) { + static void setSyntaxTree(JDTWarning warning, Tree tree) { if (warning.syntaxTree == null || isMorePreciseTree(warning.syntaxTree, tree)) { warning.syntaxTree = tree; } } @VisibleForTesting - static boolean isInsideTree(JWarning warning, Tree tree) { + static boolean isInsideTree(JDTWarning warning, Tree tree) { if (!WARNING_KINDS.getOrDefault(warning.type(), Set.of()).contains(tree.kind())) { // wrong kind return false; @@ -194,7 +197,7 @@ static boolean isMorePreciseTree(Tree currentTree, Tree newTree) { } @VisibleForTesting - static boolean matchesTreeExactly(JWarning warning) { + static boolean matchesTreeExactly(JDTWarning warning) { Tree syntaxTree = warning.syntaxTree(); return warning.start().compareTo(startOf(syntaxTree)) == 0 && warning.end().compareTo(endOf(syntaxTree)) == 0; diff --git a/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java b/java-frontend/src/main/java/org/sonar/java/jdt/ProgressMonitor.java similarity index 97% rename from java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java rename to java-frontend/src/main/java/org/sonar/java/jdt/ProgressMonitor.java index 74e3e81452c..7d1eaef44a5 100644 --- a/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/ProgressMonitor.java @@ -14,7 +14,7 @@ * 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; +package org.sonar.java.jdt; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -22,9 +22,10 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonar.java.AnalysisProgress; import org.sonar.java.annotations.VisibleForTesting; -public class ProgressMonitor implements IProgressMonitor, Runnable { +class ProgressMonitor implements IProgressMonitor, Runnable { private final Logger logger; private final long period; diff --git a/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java b/java-frontend/src/main/java/org/sonar/java/jdt/package-info.java similarity index 75% rename from java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java rename to java-frontend/src/main/java/org/sonar/java/jdt/package-info.java index 1f7680c909e..0de3f806dae 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/package-info.java @@ -14,14 +14,8 @@ * 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.model; +@javax.annotation.ParametersAreNonnullByDefault +@MethodsAreNonnullByDefault +package org.sonar.java.jdt; -import org.eclipse.jdt.core.dom.IPackageBinding; - -final class JPackageSymbol extends JSymbol { - - JPackageSymbol(JSema sema, IPackageBinding binding) { - super(sema, binding); - } - -} +import org.sonar.plugins.java.api.tree.MethodsAreNonnullByDefault; diff --git a/java-frontend/src/main/java/org/sonar/java/model/JLabelSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JLabelSymbol.java index 0754956962d..7dc960e9f58 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JLabelSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JLabelSymbol.java @@ -19,23 +19,30 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import javax.annotation.Nullable; 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.LabeledStatementTree; -import javax.annotation.Nullable; - public class JLabelSymbol extends Symbols.DefaultSymbol implements Symbol.LabelSymbol { private final String name; - LabeledStatementTree declaration; - final List usages = new ArrayList<>(); + private LabeledStatementTree declaration; + private final List usages = new ArrayList<>(); - JLabelSymbol(String name) { + public JLabelSymbol(String name) { this.name = name; } + public void setDeclaration(LabeledStatementTree declaration) { + this.declaration = declaration; + } + + public void addUsage(IdentifierTree usage) { + usages.add(usage); + } + @Override public String name() { return name; diff --git a/java-frontend/src/main/java/org/sonar/java/model/JNullabilityData.java b/java-frontend/src/main/java/org/sonar/java/model/JNullabilityData.java new file mode 100644 index 00000000000..ad65ceae05b --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/JNullabilityData.java @@ -0,0 +1,123 @@ +/* + * 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.model; + +import java.util.Arrays; +import java.util.function.Function; +import java.util.function.Predicate; +import javax.annotation.Nullable; +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.Tree; + +public final class JNullabilityData implements NullabilityData { + + private static final NullabilityData[] NO_ANNOTATION_NULLABILITY = + forEachLevel(nullabilityLevel -> new JNullabilityData(NullabilityType.NO_ANNOTATION, nullabilityLevel, null, null, false)); + + private static final NullabilityData[] UNKNOWN_NULLABILITY = + forEachLevel(nullabilityLevel -> new JNullabilityData(NullabilityType.UNKNOWN, nullabilityLevel, null, null, false)); + + private static NullabilityData[] forEachLevel(Function initializer) { + return Arrays.stream(NullabilityLevel.values()).map(initializer).toArray(NullabilityData[]::new); + } + + public static NullabilityData noNullabilityAnnotationAt(NullabilityLevel level) { + return NO_ANNOTATION_NULLABILITY[level.ordinal()]; + } + + public static NullabilityData unknownNullabilityAt(NullabilityLevel level) { + return UNKNOWN_NULLABILITY[level.ordinal()]; + } + + private final NullabilityType type; + private final NullabilityLevel level; + + @Nullable + private final AnnotationInstance annotation; + @Nullable + private final AnnotationTree annotationTree; + + private final boolean metaAnnotation; + + public JNullabilityData(NullabilityType type, NullabilityLevel level, @Nullable AnnotationInstance annotation, @Nullable AnnotationTree annotationTree, + boolean metaAnnotation) { + this.type = type; + this.level = level; + this.annotation = annotation; + this.annotationTree = annotationTree; + this.metaAnnotation = metaAnnotation; + } + + @Override + public NullabilityType type() { + return type; + } + + @Override + public NullabilityLevel level() { + return level; + } + + @Override + public boolean metaAnnotation() { + return metaAnnotation; + } + + @Override + public boolean isNonNull(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { + return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, t -> t == NullabilityType.NON_NULL); + } + + @Override + public boolean isNullable(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { + return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, + t -> t == NullabilityType.STRONG_NULLABLE || t == NullabilityType.WEAK_NULLABLE); + } + + @Override + public boolean isStrongNullable(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { + return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, t -> t == NullabilityType.STRONG_NULLABLE); + } + + private boolean testNullabilityType(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue, Predicate typePredicate) { + if (type == NullabilityType.NO_ANNOTATION) { + return false; + } else if (type == NullabilityType.UNKNOWN || (ignoreMetaAnnotation && metaAnnotation)) { + return defaultValue; + } else if (typePredicate.test(type)) { + return minLevel.ordinal() <= level.ordinal(); + } + return false; + } + + @Nullable + @Override + public AnnotationInstance annotation() { + return annotation; + } + + @Nullable + @Override + public Tree declaration() { + return annotationTree; + } +} + 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 a0d5af34a00..02dd10b3c52 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,2889 +16,12 @@ */ 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.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; +public interface JParser { -@SuppressWarnings({"rawtypes", "unchecked"}) -public class JParser { + JavaTree.CompilationUnitTreeImpl parse(String version, String unitName, String source); - private static final Logger LOG = LoggerFactory.getLogger(JParser.class); - - /** - * @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(JProblem::isSyntaxError).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); - } - - JParser converter = new JParser(); - converter.sema = new JSema(astNode.getAST()); - converter.compilationUnit = astNode; - converter.tokenManager = createTokenManager(version, unitName, source); - converter.lineColumnConverter = lineColumnConverter; - - JavaTree.CompilationUnitTreeImpl tree = converter.convertCompilationUnit(astNode); - tree.setSemanticModel(converter.sema, JProblem.semanticProblems(errors), converter.sema.getEnvironmentCleaner()); - 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.setSymbol(sema.importSymbolOrUnknown(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); - - ITypeBinding binding = e.resolveBinding(); - t.setSymbol(sema.typeSymbolOrUnknown(binding)); - declaration(binding, 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); - - ITypeBinding binding = e.resolveBinding(); - t.setSymbol(sema.typeSymbolOrUnknown(binding)); - declaration(binding, 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) - ); - ITypeBinding binding = e.getAnonymousClassDeclaration().resolveBinding(); - classBody.setSymbol(sema.typeSymbolOrUnknown(binding)); - declaration(binding, 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()); - IMethodBinding constructorBinding; - if (e.getAnonymousClassDeclaration() == null) { - constructorBinding = excludeRecovery(e.resolveConstructorBinding(), arguments.size()); - } else { - constructorBinding = findConstructorForAnonymousClass(e.getAST(), e.getName().resolveTypeBinding(), e.resolveConstructorBinding()); - } - identifier.setSymbol(sema.symbolOrUnknown(constructorBinding)); - usage(constructorBinding, identifier); - - EnumConstantTreeImpl t = new EnumConstantTreeImpl( - convertModifiers(e.modifiers()), - identifier, - new NewClassTreeImpl(identifier, arguments, classBody), - separatorToken - ); - IVariableBinding binding = e.resolveVariable(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - declaration(binding, 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())); - - IMethodBinding binding = e.resolveBinding(); - t.setSymbol(sema.methodSymbolOrUnknown(binding)); - declaration(binding, 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(), returnType.resolveBinding()), - createSimpleName(e.getName()), - formalParameters, - throwsToken, - thrownExceptionTypes, - body == null ? null : convertBlock(body), - semcolonToken - ).completeWithModifiers( - convertModifiers(e.modifiers()) - ).completeWithTypeParameters( - convertTypeParameters(e.typeParameters()) - ); - IMethodBinding binding = e.resolveBinding(); - t.setSymbol(sema.methodSymbolOrUnknown(binding)); - declaration(binding, 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(), fieldDeclaration.getType().resolveBinding())); - - 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)); - IVariableBinding binding = fragment.resolveBinding(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - declaration(binding, 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.setSymbol(sema.symbolOrUnknown(e.resolveBinding())); - return t; - } - - /** - * @param extraDimensions list of {@link org.eclipse.jdt.core.dom.Dimension} - */ - private TypeTree applyExtraDimensions(TypeTree type, List extraDimensions, @Nullable ITypeBinding 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).setSymbolType(sema.typeOrUnknown(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()); - ITypeBinding typeBinding = e.getType().resolveBinding(); - type = applyExtraDimensions(type, e.extraDimensions(), typeBinding); - if (typeBinding != null && !e.extraDimensions().isEmpty()) { - typeBinding = typeBinding.createArrayType(e.extraDimensions().size()); - } - if (e.isVarargs()) { - type = new JavaTree.ArrayTypeTreeImpl( - type, - (List) convertAnnotations(e.varargsAnnotations()), - firstTokenAfter(e.getType(), TerminalToken.TokenNameELLIPSIS) - ); - if (typeBinding != null) { - ((JavaTree.ArrayTypeTreeImpl) type).setSymbolType(sema.typeOrUnknown(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()) - ); - } - IVariableBinding binding = e.resolveBinding(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - if (binding != null) { - if (t.type() instanceof InferedTypeTree inferredType) { - inferredType.setSymbolType(sema.typeOrUnknown(binding.getType())); - } - declaration(binding, 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(), e2.getType().resolveBinding())); - } else { - t.completeTypeAndInitializer( - applyExtraDimensions(type, fragment.extraDimensions(), e2.getType().resolveBinding()), - firstTokenBefore(fragment.getInitializer(), TerminalToken.TokenNameEQUAL), - convertExpression(fragment.getInitializer()) - ); - } - if (i < e2.fragments().size() - 1) { - t.setEndToken(firstTokenAfter(fragment, TerminalToken.TokenNameCOMMA)); - } - IVariableBinding binding = fragment.resolveBinding(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - declaration(binding, t); - list.add(t); - } - } - - private VarTypeTreeImpl convertVarType(SimpleType simpleType) { - VarTypeTreeImpl varTree = new VarTypeTreeImpl(firstTokenIn(simpleType.getName(), TerminalToken.TokenNameIdentifier)); - varTree.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(e.resolveTypeBinding())); - t.setSymbol(sema.symbolOrUnknown(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(), e.getType().resolveBinding())) - .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)); - - IVariableBinding binding = fragment.resolveBinding(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - declaration(binding, 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), sema.typeOrUnknown(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), sema.typeOrUnknown(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()), - sema.typeOrUnknown(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 - ); - IMethodBinding binding = e.resolveConstructorBinding(); - if (binding != null) { - t.setSymbolType(sema.typeOrUnknown(binding.getDeclaringClass())); - binding = excludeRecovery(binding, arguments.size()); - } - t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); - i.setSymbol(sema.symbolOrUnknown(binding)); - usage(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 - ); - IMethodBinding binding = e.resolveConstructorBinding(); - if (binding != null) { - t.setSymbolType(sema.typeOrUnknown(binding.getDeclaringClass())); - binding = excludeRecovery(binding, arguments.size()); - } - t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); - i.setSymbol(sema.symbolOrUnknown(binding)); - usage(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).setSymbolType(sema.typeOrUnknown(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(e.resolveBinding(), t); - return t; - } - - private MemberSelectExpressionTreeImpl convertQualifiedName(QualifiedName e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), rhs); - return new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - rhs - ); - } - - private MemberSelectExpressionTreeImpl convertFieldAccess(FieldAccess e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), rhs); - return new MemberSelectExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT), - rhs - ); - } - - private MemberSelectExpressionTreeImpl convertFieldAccess(SuperFieldAccess e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), 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()); - ITypeBinding qualifierBinding = e.getQualifier().resolveTypeBinding(); - KeywordSuper keywordSuper = new KeywordSuper(firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamesuper), sema.superSymbolOrUnknown(qualifierBinding)); - MemberSelectExpressionTreeImpl qualifiedSuper = new MemberSelectExpressionTreeImpl( - (ExpressionTree) qualifier, - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - keywordSuper - ); - if (qualifierBinding != null) { - qualifiedSuper.setSymbolType(sema.typeOrUnknown(qualifierBinding.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), sema.thisSymbolOrUnknown(e.resolveTypeBinding())); - } - KeywordThis keywordThis = new KeywordThis( - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamethis), - sema.thisSymbolOrUnknown(e.resolveTypeBinding()) - ); - keywordThis.setSymbolType(sema.typeOrUnknown(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) - ); - ITypeBinding binding = e.getAnonymousClassDeclaration().resolveBinding(); - classBody.setSymbol(sema.typeSymbolOrUnknown(binding)); - declaration(binding, 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(); - IMethodBinding binding; - if (e.getAnonymousClassDeclaration() == null) { - binding = excludeRecovery(e.resolveConstructorBinding(), nbArguments); - } else { - Type constructorType = e.getType() instanceof ParameterizedType parameterizedType ? parameterizedType.getType() : e.getType(); - binding = excludeRecovery(findConstructorForAnonymousClass(e.getAST(), constructorType.resolveBinding(), e.resolveConstructorBinding()), nbArguments); - } - i.setSymbol(sema.symbolOrUnknown(binding)); - usage(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.setSymbolType(sema.typeOrUnknown(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 - ); - IMethodBinding binding = excludeRecovery(e.resolveMethodBinding(), arguments.size()); - t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); - rhs.setSymbol(sema.symbolOrUnknown(binding)); - usage(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()); - ITypeBinding qualifierBinding = e.getQualifier().resolveTypeBinding(); - KeywordSuper keywordSuper = new KeywordSuper(firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamesuper), sema.superSymbolOrUnknown(qualifierBinding)); - MemberSelectExpressionTreeImpl qualifiedSuper = new MemberSelectExpressionTreeImpl( - (ExpressionTree) qualifier, - createSyntaxToken(firstDotTokenIndex), - keywordSuper - ); - if (qualifierBinding != null) { - qualifiedSuper.setSymbolType(sema.typeOrUnknown(qualifierBinding.getSuperclass())); - } - outermostSelect = new MemberSelectExpressionTreeImpl( - qualifiedSuper, - createSyntaxToken(nextTokenIndex(firstDotTokenIndex, TerminalToken.TokenNameDOT)), - rhs - ); - } - - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - outermostSelect, - null, - arguments - ); - IMethodBinding binding = excludeRecovery(e.resolveMethodBinding(), arguments.size()); - t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); - rhs.setSymbol(sema.symbolOrUnknown(binding)); - usage(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 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.setSymbol(sema.variableSymbolOrUnknown(variableBinding)); - ((InferedTypeTree) t.type()).setSymbolType(sema.typeOrUnknown(variableBinding.getType())); - declaration(variableBinding, t); - } - } 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.setSymbol(sema.methodSymbolOrUnknown(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)); - IMethodBinding binding = e.resolveMethodBinding(); - i.setSymbol(sema.symbolOrUnknown(binding)); - usage(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(e.getName().resolveBinding(), 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(e.getName().resolveBinding(), 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(e.getName().resolveBinding(), 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).setSymbolType(sema.typeOrUnknown(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, sema.superSymbolOrUnknown(abstractTypeDeclaration.resolveBinding())); - } - if (node instanceof AnonymousClassDeclaration anonymousClassDeclaration) { - return new KeywordSuper(token, sema.superSymbolOrUnknown(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.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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).setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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.setSymbolType(sema.typeOrUnknown(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)); - } - 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)); + static JavaTree.CompilationUnitTreeImpl parse(JParser parser, String version, String unitName, String source) { + return parser.parse(version, unitName, source); } } 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 9a1c803dcd0..ed4c88d78ad 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,271 +17,66 @@ 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 javax.annotation.Nullable; 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.java.jdt.JDTParserConfig; import org.sonar.plugins.java.api.JavaVersion; -import org.sonarsource.analyzer.commons.ProgressReport; -import org.sonarsource.performance.measure.PerformanceMeasure; -public abstract class JParserConfig { +public interface JParserConfig { - public static final JavaVersion MAXIMUM_SUPPORTED_JAVA_VERSION = new JavaVersionImpl(JavaVersionImpl.MAX_SUPPORTED, true); + JavaVersion MAXIMUM_SUPPORTED_JAVA_VERSION = new JavaVersionImpl(JavaVersionImpl.MAX_SUPPORTED, true); - private static final Logger LOG = LoggerFactory.getLogger(JParserConfig.class); + JParser astParser(); - 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")); + JavaTree.CompilationUnitTreeImpl parse(String unitName, String source); - final JavaVersion javaVersion; - final List classpath; - final boolean shouldIgnoreUnnamedModuleForSplitPackage; - - private JParserConfig(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { - this.javaVersion = javaVersion; - this.classpath = classpath; - this.shouldIgnoreUnnamedModuleForSplitPackage = shouldIgnoreUnnamedModuleForSplitPackage; - } - - public abstract void parse(Iterable inputFiles, BooleanSupplier isCanceled, + void parse(Iterable inputFiles, BooleanSupplier isCanceled, AnalysisProgress analysisProgress, BiConsumer action); - public enum Mode { - BATCH(Batch::new), - FILE_BY_FILE(FileByFile::new); - - private final ParserConfigConstructor supplier; - - Mode(ParserConfigConstructor supplier) { - this.supplier = supplier; - } + enum Mode { + BATCH, + FILE_BY_FILE; public JParserConfig create(JavaVersion javaVersion, List classpath) { return create(javaVersion, classpath, false); } public JParserConfig create(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { - if (shouldIgnoreUnnamedModuleForSplitPackage) { - LOG.info("The Java analyzer will ignore the unnamed module for split packages."); - } - return supplier.apply(javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); + return JDTParserConfig.create(this, javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); } } - public static class Result { - private final Exception e; - private final JavaTree.CompilationUnitTreeImpl t; - - private Result(Exception e) { - this.e = e; - this.t = null; - } - - private Result(JavaTree.CompilationUnitTreeImpl t) { - this.e = null; - this.t = t; - } + final class Result { + private final Exception exception; + private final JavaTree.CompilationUnitTreeImpl tree; - public JavaTree.CompilationUnitTreeImpl get() throws Exception { - if (e != null) { - throw e; - } - return t; + private Result(@Nullable Exception exception, @Nullable JavaTree.CompilationUnitTreeImpl tree) { + this.exception = exception; + this.tree = tree; } - } - 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"); + public static Result success(JavaTree.CompilationUnitTreeImpl tree) { + return new Result(null, tree); } - if (shouldEnablePreviewFlag(javaVersion)) { - options.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled"); - } - // enabling all supported compiler warnings - JProblem.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 { - Batch(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { - super(javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); + public static Result failure(Exception exception) { + return new Result(exception, null); } - @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(); - } - }, 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); - } - } 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); - } - } - - } - - private static class FileByFile extends JParserConfig { - - private FileByFile(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { - super(javaVersion, classpath, shouldIgnoreUnnamedModuleForSplitPackage); - } - - @Override - public void parse(Iterable inputFiles, BooleanSupplier isCanceled, - AnalysisProgress analysisProgress, BiConsumer action) { - boolean successfullyCompleted = false; - boolean cancelled = false; - - ExecutionTimeReport executionTimeReport = new ExecutionTimeReport(); - ProgressReport progressReport = new ProgressReport("Report about progress of Java AST analyzer", TimeUnit.SECONDS.toMillis(10)); - List filesNames = StreamSupport.stream(inputFiles.spliterator(), false) - .map(InputFile::toString) - .toList(); - progressReport.start(filesNames); - try { - for (InputFile inputFile : inputFiles) { - if (isCanceled.getAsBoolean()) { - cancelled = true; - break; - } - executionTimeReport.start(inputFile); - parse(astParser(), inputFile, javaVersion, action); - executionTimeReport.end(); - progressReport.nextFile(); - } - successfullyCompleted = !cancelled; - } finally { - if (successfullyCompleted) { - progressReport.stop(); - } else { - progressReport.cancel(); - } - 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(); + public JavaTree.CompilationUnitTreeImpl get() throws Exception { + if (exception != null) { + throw exception; } - action.accept(inputFile, result); + return tree; } } - @VisibleForTesting - static boolean shouldEnablePreviewFlag(JavaVersion currentVersion) { - return currentVersion.arePreviewFeaturesEnabled(); - } @FunctionalInterface - public interface ParserConfigConstructor { + interface ParserConfigConstructor { JParserConfig apply(JavaVersion version, List files, Boolean shouldIgnoreUnnamedModuleForSplitPackage); } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java index d11a02408f8..3d6d2dda7fe 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java @@ -38,8 +38,8 @@ import org.sonar.plugins.java.api.semantic.Type; import org.sonarsource.analyzer.commons.collections.SetUtils; -import static org.sonar.java.model.JSymbolMetadata.noNullabilityAnnotationAt; -import static org.sonar.java.model.JSymbolMetadata.unknownNullabilityAt; +import static org.sonar.java.model.JNullabilityData.noNullabilityAnnotationAt; +import static org.sonar.java.model.JNullabilityData.unknownNullabilityAt; import static org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityLevel.CLASS; import static org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityLevel.PACKAGE; import static org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityLevel.VARIABLE; @@ -327,7 +327,7 @@ private static NullabilityData getNullabilityData(SymbolMetadata metadata, } else if (annotationInstance == null) { return noNullabilityAnnotationAt(level); } - return new JSymbolMetadata.JNullabilityData(nullabilityType, level, + return new JNullabilityData(nullabilityType, level, annotationInstance, metadata.findAnnotationTree(annotationInstance), isMetaAnnotated); } 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..ade45b6130a 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,40 +25,36 @@ 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; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.Tree; -import org.sonarsource.analyzer.commons.collections.MapBuilder; public final class JUtils { private JUtils() { } - static final Map WRAPPER_TO_PRIMITIVE = MapBuilder.newMap() - .put("java.lang.Byte", "byte") - .put("java.lang.Character", "char") - .put("java.lang.Short", "short") - .put("java.lang.Integer", "int") - .put("java.lang.Long", "long") - .put("java.lang.Float", "float") - .put("java.lang.Double", "double") - .put("java.lang.Boolean", "boolean") - .build(); - - static final Map PRIMITIVE_TO_WRAPPER = MapBuilder.newMap() - .put("byte", "java.lang.Byte") - .put("char", "java.lang.Character") - .put("short", "java.lang.Short") - .put("int", "java.lang.Integer") - .put("long","java.lang.Long") - .put("float", "java.lang.Float") - .put("double", "java.lang.Double") - .put("boolean", "java.lang.Boolean") - .build(); + public static final Map WRAPPER_TO_PRIMITIVE = Map.of( + "java.lang.Byte", "byte", + "java.lang.Character", "char", + "java.lang.Short", "short", + "java.lang.Integer", "int", + "java.lang.Long", "long", + "java.lang.Float", "float", + "java.lang.Double", "double", + "java.lang.Boolean", "boolean"); + + public static final Map PRIMITIVE_TO_WRAPPER = Map.of( + "byte", "java.lang.Byte", + "char", "java.lang.Character", + "short", "java.lang.Short", + "int", "java.lang.Integer", + "long", "java.lang.Long", + "float", "java.lang.Float", + "double", "java.lang.Double", + "boolean", "java.lang.Boolean"); public static Type wrapTypeIfPrimitive(Type type) { Type wrapped = type.primitiveWrapperType(); @@ -67,19 +62,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.isUnknown() && type.isIntersectionType(); } public static Symbol getPackage(Symbol symbol) { @@ -94,7 +77,7 @@ public static Object defaultValue(Symbol.MethodSymbol method) { if (method.isUnknown()) { return null; } - return ((JMethodSymbol) method).methodBinding().getDefaultValue(); + return method.annotationElementDefaultValue(); } /** @@ -103,7 +86,7 @@ public static Object defaultValue(Symbol.MethodSymbol method) { * *

Recording both means a lookup by erased name still finds every type it finds today, while a lookup by * parameterized key only finds the types actually declared with that parameterization. - * + *

* {@code java.lang.Object} and unknown/unresolved types are excluded. Recursion is guarded by erased name, so * a common ancestor reached through several interfaces is walked once, and a self-referential generic such as * {@code interface A extends B>} still terminates. @@ -162,14 +145,12 @@ public static Set directSuperTypes(Type type) { return Collections.emptySet(); } Set result = new HashSet<>(); - JType t = (JType) type; - ITypeBinding superclass = t.typeBinding.getSuperclass(); + Symbol.TypeSymbol symbol = type.symbol(); + Type superclass = (symbol.isInterface() || type.isArray()) ? null : symbol.superClass(); if (superclass != null) { - result.add(t.sema.type(superclass)); + result.add(superclass); } - Arrays.stream(t.typeBinding.getInterfaces()) - .map(t.sema::type) - .forEach(result::add); + result.addAll(symbol.interfaces()); return result; } @@ -222,10 +203,10 @@ public static boolean hasUnknownTypeInHierarchy(Symbol.TypeSymbol typeSymbol) { } /** - * @param typeSymbol to lookup recursively for unknown symbol on itself, its interfaces and its super classes hierarchy. + * @param typeSymbol to lookup recursively for unknown symbol on itself, its interfaces and its super classes hierarchy. * @param methodSymbol a method having at least one unknown parameter type that prevent the overriding resolution - * to find any exact match. This methodSymbol will be used to find a potential match, just by - * comparing the name and parameter count. + * to find any exact match. This methodSymbol will be used to find a potential match, just by + * comparing the name and parameter count. * @return true if the given typeSymbol and its inheritance hierarchy is 100% known and no method in the inheritance * cloud match (name and parameter count) the given methodSymbol. In this case, we are sure the methodSymbol does not * override any another methods. 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 6bd879ea8cb..cea2c3fae35 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 @@ -20,9 +20,9 @@ import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; -final class KeywordSuper extends IdentifierTreeImpl { +public final class KeywordSuper extends IdentifierTreeImpl { - KeywordSuper(InternalSyntaxToken token, Symbol symbol) { + public KeywordSuper(InternalSyntaxToken token, Symbol symbol) { super(token); setSymbol(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 90a35137502..6080f849c91 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 @@ -19,9 +19,9 @@ import org.sonar.java.model.expression.IdentifierTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; -final class KeywordThis extends IdentifierTreeImpl { +public final class KeywordThis extends IdentifierTreeImpl { - KeywordThis(InternalSyntaxToken token, Symbol symbol) { + public KeywordThis(InternalSyntaxToken token, Symbol symbol) { super(token); setSymbol(symbol); } diff --git a/java-frontend/src/main/java/org/sonar/java/model/Symbols.java b/java-frontend/src/main/java/org/sonar/java/model/Symbols.java index 767e1a9dbf2..6bb57688f82 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/Symbols.java +++ b/java-frontend/src/main/java/org/sonar/java/model/Symbols.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Set; import javax.annotation.CheckForNull; - import javax.annotation.Nullable; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; @@ -66,12 +65,12 @@ public List symbolAnnotations() { @Override public NullabilityData nullabilityData() { - return JSymbolMetadata.unknownNullabilityAt(NullabilityLevel.UNKNOWN); + return JNullabilityData.unknownNullabilityAt(NullabilityLevel.UNKNOWN); } @Override public NullabilityData nullabilityData(NullabilityTarget level) { - return JSymbolMetadata.unknownNullabilityAt(NullabilityLevel.UNKNOWN); + return JNullabilityData.unknownNullabilityAt(NullabilityLevel.UNKNOWN); } @Nullable diff --git a/java-frontend/src/main/java/org/sonar/java/utils/SpringUtils.java b/java-frontend/src/main/java/org/sonar/java/utils/SpringUtils.java index 714fc59d889..454456cfba1 100644 --- a/java-frontend/src/main/java/org/sonar/java/utils/SpringUtils.java +++ b/java-frontend/src/main/java/org/sonar/java/utils/SpringUtils.java @@ -26,7 +26,6 @@ import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; - import org.sonar.java.model.ExpressionUtils; import org.sonar.java.model.JUtils; import org.sonar.java.model.springcontext.InjectionPoint; diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/Symbol.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/Symbol.java index e17e57a418f..2a23ffec7d1 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/Symbol.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/Symbol.java @@ -241,12 +241,22 @@ interface MethodSymbol extends Symbol { * Use to access parameter annotations. Note: * 1) in case of generic types of method invocations, this list of types is less specialized than {@link #parameterTypes()}. * For example it could be T instead of String. - * 2) when the declaration comes from binaries, the name of the symbol will be generated (@see JVariableSymbol.ParameterPlaceholderSymbol). + * 2) when the declaration comes from binaries, the name of the symbol will be generated. * * @return empty list if methods has not parameters */ List declarationParameters(); + /** + * Returns an annotation element's default value, or null if it has none. + * Values use the same representation as {@link SymbolMetadata.AnnotationValue#value()}: + * constants, type symbols, variable symbols, annotation instances, or arrays of these values. + */ + @Nullable + default Object annotationElementDefaultValue() { + return null; + } + TypeSymbol returnType(); /** diff --git a/java-frontend/src/test/java/org/sonar/java/FrontendApiTest.java b/java-frontend/src/test/java/org/sonar/java/FrontendApiTest.java new file mode 100644 index 00000000000..392efac8721 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/FrontendApiTest.java @@ -0,0 +1,134 @@ +/* + * 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; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.java.model.JNullabilityData; +import org.sonar.java.model.JParser; +import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JParserConfig.Result; +import org.sonar.java.model.JSymbolMetadataNullabilityHelper; +import org.sonar.java.model.JUtils; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.Symbols; +import org.sonar.plugins.java.api.diagnostics.Problem; +import org.sonar.plugins.java.api.diagnostics.Warning; +import org.sonar.plugins.java.api.semantic.Sema; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.SymbolMetadata; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityLevel; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityTarget; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.VariableTree; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class FrontendApiTest { + + @Test + void original_single_file_call_resolves_semantics_through_frontend_interfaces() { + var version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; + JParserConfig.ParserConfigConstructor constructor = JParserConfig.Mode.FILE_BY_FILE::create; + JParserConfig config = constructor.apply(version, List.of(), false); + JParser parser = config.astParser(); + JavaTree.CompilationUnitTreeImpl tree = JParser.parse(parser, version.toString(), "Sample.java", "class Sample { String value; }"); + try { + ClassTree declaration = (ClassTree) tree.types().get(0); + Symbol.TypeSymbol symbol = declaration.symbol(); + Type type = symbol.type(); + VariableTree field = (VariableTree) declaration.members().get(0); + assertThat(symbol.name()).isEqualTo("Sample"); + assertThat(type.isSubtypeOf("java.lang.Object")).isTrue(); + assertThat(field.symbol().type().is("java.lang.String")).isTrue(); + assertThat(tree.sema.getClassType("java.lang.String").isUnknown()).isFalse(); + } finally { + tree.getEnvironmentCleaner().run(); + } + } + + @ParameterizedTest + @EnumSource(JParserConfig.Mode.class) + void original_batch_callback_uses_nested_result(JParserConfig.Mode mode) { + InputFile input = TestUtils.inputFile("src/test/files/metrics/Classes.java"); + AtomicInteger callbacks = new AtomicInteger(); + mode.create(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, List.of()) + .parse(List.of(input), () -> false, new AnalysisProgress(1), (file, result) -> { + assertThat(file).isSameAs(input); + try { + JavaTree.CompilationUnitTreeImpl tree = result.get(); + assertThat(tree.types()).isNotEmpty(); + tree.getEnvironmentCleaner().run(); + } catch (Exception e) { + throw new AssertionError(e); + } + callbacks.incrementAndGet(); + }); + assertThat(callbacks.get()).isEqualTo(1); + } + + @Test + void nested_result_rethrows_original_exception() { + Exception exception = new IOException("failed to read source"); + Result result = Result.failure(exception); + assertThatThrownBy(result::get).isSameAs(exception); + } + + @Test + void original_utility_names_remain_available() { + assertThat(JUtils.defaultValue(Symbol.MethodSymbol.UNKNOWN_METHOD)).isNull(); + assertThat(JUtils.wrapTypeIfPrimitive(Type.UNKNOWN)).isSameAs(Type.UNKNOWN); + assertThat(JNullabilityData.noNullabilityAnnotationAt(NullabilityLevel.CLASS)) + .isSameAs(JSymbolMetadataNullabilityHelper.getNullabilityDataAtLevel(Symbols.EMPTY_METADATA, NullabilityTarget.METHOD, NullabilityLevel.CLASS)); + assertThat(JNullabilityData.unknownNullabilityAt(NullabilityLevel.PACKAGE).level()).isEqualTo(NullabilityLevel.PACKAGE); + } + + @Test + void frontend_contracts_are_interfaces() { + assertThat(List.of(JParser.class, JParserConfig.class, Sema.class, Type.class, Symbol.class, + Symbol.TypeSymbol.class, Symbol.MethodSymbol.class, Symbol.VariableSymbol.class, + SymbolMetadata.class, Problem.class, Warning.class)).allMatch(Class::isInterface); + } + + @Test + void shared_frontend_classes_do_not_depend_on_jdt() throws Exception { + Path sonarClasses = Path.of(JParser.class.getProtectionDomain().getCodeSource().getLocation().toURI()).resolve("org/sonar"); + try (var classes = Files.walk(sonarClasses)) { + for (Path path : classes.filter(file -> file.toString().endsWith(".class")).toList()) { + Path relative = sonarClasses.relativize(path); + if (relative.startsWith(Path.of("java", "jdt"))) { + continue; + } + String bytecode = Files.readString(path, StandardCharsets.ISO_8859_1); + assertThat(bytecode).as("%s", relative).doesNotContain("org/eclipse/jdt/", "org/eclipse/core/"); + if (!relative.equals(Path.of("java", "model", "JParserConfig$Mode.class"))) { + assertThat(bytecode).as("%s", relative).doesNotContain("org/sonar/java/jdt/"); + } + } + } + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java b/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java index 7566a61a793..19e17c78e1a 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java @@ -47,7 +47,7 @@ import org.sonar.java.classpath.ClasspathForMain; import org.sonar.java.classpath.ClasspathForTest; import org.sonar.java.exceptions.ApiMismatchException; -import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JParserConfig.Result; import org.sonar.java.model.JParserTestUtils; import org.sonar.java.model.JavaTree; import org.sonar.java.model.JavaVersionImpl; @@ -402,7 +402,7 @@ void skippableVisitors_are_not_used_when_file_is_unchanged() throws ApiMismatchE jas.setVisitorBridge(visitorsBridge); InputFile inputFile = new TestInputFileBuilder("class A {}", "file.java").build(); - jas.simpleScan(inputFile, mock(JParserConfig.Result.class), ignored -> {}); + jas.simpleScan(inputFile, mock(Result.class), ignored -> {}); verify(skippable, never()).visitNode(any()); verify(unskippable, times(1)).visitNode(any()); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JMethodSymbolTest.java similarity index 78% rename from java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JMethodSymbolTest.java index 23d7f0f21b4..b659e5daa04 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JMethodSymbolTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.ArrayList; import java.util.Arrays; @@ -28,7 +28,11 @@ import org.sonar.api.batch.fs.InputFile; import org.sonar.java.AnalysisProgress; import org.sonar.java.TestUtils; +import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; import org.sonar.java.model.JavaTree.CompilationUnitTreeImpl; +import org.sonar.java.model.Symbols; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.expression.MethodInvocationTreeImpl; @@ -37,6 +41,7 @@ import org.sonar.java.model.statement.ReturnStatementTreeImpl; import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationInstance; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; import org.sonar.plugins.java.api.tree.LambdaExpressionTree; @@ -52,28 +57,68 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.sonar.java.model.JParserTestUtils.methodBinding; -import static org.sonar.java.model.JParserTestUtils.sema; +import static org.sonar.java.jdt.JUtilsTest.firstClass; +import static org.sonar.java.jdt.JUtilsTest.firstMethod; +import static org.sonar.java.jdt.JUtilsTest.nthMethod; +import static org.sonar.java.jdt.JdtTestUtils.methodBinding; +import static org.sonar.java.jdt.JdtTestUtils.sema; import static org.sonar.java.model.JParserTestUtils.symbolOf; import static org.sonar.java.model.JParserTestUtils.typeOf; -import static org.sonar.java.model.JUtilsTest.firstClass; -import static org.sonar.java.model.JUtilsTest.firstMethod; -import static org.sonar.java.model.JUtilsTest.nthMethod; class JMethodSymbolTest { + @Test + void annotation_default_values_use_semantic_api_types() { + var tree = test(""" + @interface Nested { String value(); } + enum Choice { FIRST } + @interface Defaults { + int number() default 42; + String text() default "value"; + Class type() default String.class; + Choice choice() default Choice.FIRST; + Nested nested() default @Nested("value"); + Class[] types() default {String.class, int.class}; + Choice[] choices() default {Choice.FIRST}; + Nested[] annotations() default {@Nested("value")}; + int[] numbers() default {1, 2}; + String absent(); + } + """); + var defaults = ((ClassTreeImpl) tree.types().get(2)).symbol(); + + assertThat(annotationElementDefaultValue(defaults, "number")).isEqualTo(42); + assertThat(annotationElementDefaultValue(defaults, "text")).isEqualTo("value"); + assertThat(((Symbol.TypeSymbol) annotationElementDefaultValue(defaults, "type")).type().is("java.lang.String")).isTrue(); + assertThat(((Symbol.VariableSymbol) annotationElementDefaultValue(defaults, "choice")).name()).isEqualTo("FIRST"); + assertThat(((AnnotationInstance) annotationElementDefaultValue(defaults, "nested")).values().get(0).value()).isEqualTo("value"); + assertThat((Object[]) annotationElementDefaultValue(defaults, "types")) + .allSatisfy(value -> assertThat(value).isInstanceOf(Symbol.TypeSymbol.class)); + assertThat((Object[]) annotationElementDefaultValue(defaults, "choices")) + .allSatisfy(value -> assertThat(value).isInstanceOf(Symbol.VariableSymbol.class)); + assertThat((Object[]) annotationElementDefaultValue(defaults, "annotations")) + .allSatisfy(value -> assertThat(value).isInstanceOf(AnnotationInstance.class)); + assertThat((Object[]) annotationElementDefaultValue(defaults, "numbers")).containsExactly(1, 2); + assertThat(annotationElementDefaultValue(defaults, "absent")).isNull(); + assertThat(Symbol.MethodSymbol.UNKNOWN_METHOD.annotationElementDefaultValue()).isNull(); + } + + private static Object annotationElementDefaultValue(Symbol.TypeSymbol annotation, String name) { + return ((Symbol.MethodSymbol) annotation.lookupSymbols(name).iterator().next()).annotationElementDefaultValue(); + } + @Test void test() { JavaTree.CompilationUnitTreeImpl cu = test("class C { C m(C p) throws Exception { return null; } }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.parameterTypes()) - .containsOnly(((JType) typeOf(c))); + .containsOnly(((JDTType) typeOf(c))); assertThat(symbol.declarationParameters()) .containsExactly(m.parameters().get(0).symbol()); assertThat(symbol.returnType()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol())); + .isSameAs(((JDTTypeSymbol) typeOf(c).symbol())); assertThat(symbol.thrownTypes()) .containsOnly(sema(cu).type(Objects.requireNonNull(sema(cu).resolveType("java.lang.Exception")))); } @@ -84,7 +129,7 @@ void test_with_generics() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); Symbol.MethodSymbol declarationSymbol = m.symbol(); - JType declarationParameterType = sema(cu).type(Objects.requireNonNull((methodBinding(m).getParameterTypes()[0]))); + JDTType declarationParameterType = sema(cu).type(Objects.requireNonNull((methodBinding(m).getParameterTypes()[0]))); assertThat(declarationSymbol.parameterTypes()) .containsOnly(declarationParameterType); Symbol declarationParameterSymbol = m.parameters().get(0).symbol(); @@ -126,16 +171,16 @@ void test_declaration_not_in_code() { Symbol symbolParam1 = symbol1.declarationParameters().get(0); Symbol symbolParam2 = symbol2.declarationParameters().get(0); - assertThat(symbolParam1).isInstanceOf(JVariableSymbol.ParameterPlaceholderSymbol.class) + assertThat(symbolParam1).isInstanceOf(JDTVariableSymbol.ParameterPlaceholderSymbol.class) .isInstanceOf(Symbol.VariableSymbol.class); - assertThat(symbolParam2).isInstanceOf(JVariableSymbol.ParameterPlaceholderSymbol.class) + assertThat(symbolParam2).isInstanceOf(JDTVariableSymbol.ParameterPlaceholderSymbol.class) .isInstanceOf(Symbol.VariableSymbol.class); assertThat(symbolParam1.isVariableSymbol()).isTrue(); assertThat(symbolParam2.isVariableSymbol()).isTrue(); - JVariableSymbol.ParameterPlaceholderSymbol parameterSymbol1 = (JVariableSymbol.ParameterPlaceholderSymbol) symbolParam1; - JVariableSymbol.ParameterPlaceholderSymbol parameterSymbol2 = (JVariableSymbol.ParameterPlaceholderSymbol) symbolParam2; + JDTVariableSymbol.ParameterPlaceholderSymbol parameterSymbol1 = (JDTVariableSymbol.ParameterPlaceholderSymbol) symbolParam1; + JDTVariableSymbol.ParameterPlaceholderSymbol parameterSymbol2 = (JDTVariableSymbol.ParameterPlaceholderSymbol) symbolParam2; assertThat(parameterSymbol1).isSameAs(parameterSymbol2); @@ -197,7 +242,7 @@ void testSingleInheritance() { + "class Clazz implements A { public void a() {} }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(1); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.overriddenSymbols()).containsOnly(retrieveMethodSymbol("A", "a", cu)); } @@ -212,7 +257,7 @@ class C extends B { public void a() {} } """); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(3); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.overriddenSymbols()).containsExactly( retrieveMethodSymbol("B", "a", cu), @@ -225,7 +270,7 @@ void testObjectExtensionFromClass() { JavaTree.CompilationUnitTreeImpl cu = test("class Clazz { public boolean equals(Object other) { return false; } }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.overriddenSymbols()).containsExactly(objectEqualsMethod(sema(cu))); } @@ -235,12 +280,12 @@ void testObjectExtensionFromInterface() { JavaTree.CompilationUnitTreeImpl cu = test("interface Interface { boolean equals(Object other) { return false; } }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.overriddenSymbols()).containsExactly(objectEqualsMethod(sema(cu))); } - private static JMethodSymbol objectEqualsMethod(JSema sema) { + private static JDTMethodSymbol objectEqualsMethod(JDTSema sema) { return Arrays.stream(sema.resolveType("java.lang.Object").getDeclaredMethods()) .filter(method -> "equals".equals(method.getName())) .findFirst() @@ -257,7 +302,7 @@ class C implements A, B { public void a() { } } """); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(2); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.overriddenSymbols()).containsExactly( retrieveMethodSymbol("A", "a", cu), @@ -271,7 +316,7 @@ void testUnknownInheritance() { + "class C implements KnownInterface, UnknownInterface { public void a() { } }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(1); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.overriddenSymbols()).containsExactly( retrieveMethodSymbol("KnownInterface", "a", cu)); @@ -283,7 +328,7 @@ void testUnknownExtends() { + "class B extends Unknown { public void a() { } }"); ClassTreeImpl a = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) a.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.overriddenSymbols()).isEmpty(); } @@ -293,9 +338,9 @@ void testBindingReturnNullSuperClass() { + "class A { public void a() { } }"); ClassTreeImpl a = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) a.members().get(0); - // We create a real JMethodSymbol because it is not possible to mock JMethodSymbol (it is final), and not easy + // We create a real JDTMethodSymbol because it is not possible to mock JDTMethodSymbol (it is final), and not easy // to mock the arguments either, because the constructor is using an external helper method. - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); ITypeBinding iTypeBinding = mock(ITypeBinding.class); // In some rare situation, the "getSuperclass" can return null. It is not easy to write code reproducing the issue, we test it by "hand". @@ -319,7 +364,7 @@ class Clazz implements A, C { public boolean equals(Object other) { return false """); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(3); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); + JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); assertThat(symbol.overriddenSymbols()).containsExactly( objectEqualsMethod(sema(cu)), @@ -327,7 +372,7 @@ class Clazz implements A, C { public boolean equals(Object other) { return false retrieveMethodSymbol("B", "equals", cu)); } - private static JMethodSymbol retrieveMethodSymbol(String className, String methodName, CompilationUnitTreeImpl cu) { + private static JDTMethodSymbol retrieveMethodSymbol(String className, String methodName, CompilationUnitTreeImpl cu) { MethodMatchers methodMatcher = MethodMatchers.create() .ofTypes(className) .names(methodName) @@ -340,7 +385,7 @@ private static JMethodSymbol retrieveMethodSymbol(String className, String metho .map(Symbol.TypeSymbol::memberSymbols) .flatMap(Collection::stream) .filter(methodMatcher::matches) - .map(JMethodSymbol.class::cast) + .map(JDTMethodSymbol.class::cast) .findFirst() .orElseThrow(() -> new IllegalStateException("No method could be found with the given name in the given class")); } @@ -353,20 +398,20 @@ void signature() { MethodTreeImpl method = (MethodTreeImpl) c.members().get(1); ReturnStatementTreeImpl s = (ReturnStatementTreeImpl) Objects.requireNonNull(method.block()).body().get(0); MethodInvocationTreeImpl methodInvocation = Objects.requireNonNull((MethodInvocationTreeImpl) s.expression()); - assertThat(((JMethodSymbol) symbolOf(constructor)).signature()) + assertThat(((JDTMethodSymbol) symbolOf(constructor)).signature()) .isEqualTo(constructor.symbol().signature()) .isEqualTo("org.example.C#()V"); - assertThat(((JMethodSymbol) symbolOf(method)).signature()) + assertThat(((JDTMethodSymbol) symbolOf(method)).signature()) .isEqualTo(method.symbol().signature()) .isEqualTo("org.example.C#m(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - assertThat(((JMethodSymbol) symbolOf(methodInvocation)).signature()) + assertThat(((JDTMethodSymbol) symbolOf(methodInvocation)).signature()) .isEqualTo(method.symbol().signature()) .isEqualTo("org.example.C#m(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); } @Test void isOverridableTest(){ - JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + JDTMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); assertThat(jms.isOverridable()).isTrue(); jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }", true); assertThat(jms.isOverridable()).isFalse(); @@ -382,7 +427,7 @@ void isOverridableTest(){ @Test void isParametrizedMethodTest(){ - JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + JDTMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); assertThat(jms.isParametrizedMethod()).isFalse(); jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); assertThat(jms.isParametrizedMethod()).isTrue(); @@ -394,7 +439,7 @@ void isParametrizedMethodTest(){ @Test void isDefaultMethodTest(){ - JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + JDTMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); assertThat(jms.isDefaultMethod()).isFalse(); jms = getJMethodSymbolFromClassText("interface I { default C m() { return null; } }"); assertThat(jms.isDefaultMethod()).isTrue(); @@ -404,7 +449,7 @@ void isDefaultMethodTest(){ @Test void isSynchronizedMethodTest(){ - JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + JDTMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); assertThat(jms.isSynchronizedMethod()).isFalse(); jms = getJMethodSymbolFromClassText("class C { synchronized C m() { return null; } }"); assertThat(jms.isSynchronizedMethod()).isTrue(); @@ -414,7 +459,7 @@ void isSynchronizedMethodTest(){ @Test void isVarArgsTest(){ - JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + JDTMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); assertThat(jms.isVarArgsMethod()).isFalse(); jms = getJMethodSymbolFromClassText("class C { C m(String... s) { return null; } }"); assertThat(jms.isVarArgsMethod()).isTrue(); @@ -430,7 +475,7 @@ void support_unexpected_IMethodBinding_null_return_type() { // simulate broken semantic with getReturnType() == null IMethodBinding brokenMethodBinding = spy(methodBinding(m)); when(brokenMethodBinding.getReturnType()).thenReturn(null); - JMethodSymbol methodSymbol = new JMethodSymbol(sema(cu), brokenMethodBinding); + JDTMethodSymbol methodSymbol = new JDTMethodSymbol(sema(cu), brokenMethodBinding); assertThat(methodSymbol.returnType().isUnknown()).isTrue(); assertThat(methodSymbol.isNativeMethod()).isFalse(); assertThat(methodSymbol.metadata()).isSameAs(Symbols.EMPTY_METADATA); @@ -451,15 +496,15 @@ public static void f() { MethodTreeImpl m = (MethodTreeImpl) c.members().get(1); ExpressionStatementTree statementTree = ((ExpressionStatementTreeImpl) m.block().body().get(0)); NewClassTreeImpl newClassExpression = ((NewClassTreeImpl) statementTree.expression()); - JMethodSymbol symbol = ((JMethodSymbol) newClassExpression.methodSymbol()); + JDTMethodSymbol symbol = ((JDTMethodSymbol) newClassExpression.methodSymbol()); assertThat(symbol.declarationParameters()).hasSize(6); } - private static JMethodSymbol getJMethodSymbolFromClassText(String classText){ + private static JDTMethodSymbol getJMethodSymbolFromClassText(String classText){ return getJMethodSymbolFromClassText(classText, false); } - private static JMethodSymbol getJMethodSymbolFromClassText(String classText, boolean brokenSemantic){ + private static JDTMethodSymbol getJMethodSymbolFromClassText(String classText, boolean brokenSemantic){ JavaTree.CompilationUnitTreeImpl cu = test(classText); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); @@ -467,9 +512,9 @@ private static JMethodSymbol getJMethodSymbolFromClassText(String classText, boo IMethodBinding brokenMethodBinding = spy(methodBinding(m)); when(brokenMethodBinding.getReturnType()).thenReturn(null); when(brokenMethodBinding.isRecovered()).thenReturn(true); - return new JMethodSymbol(sema(cu), brokenMethodBinding); + return new JDTMethodSymbol(sema(cu), brokenMethodBinding); } - return new JMethodSymbol(sema(cu), methodBinding(m)); + return new JDTMethodSymbol(sema(cu), methodBinding(m)); } private static CompilationUnitTreeImpl test(String source) { @@ -515,13 +560,13 @@ void foo() { } ClassTreeImpl a = firstClass(cu); MethodTreeImpl foo = firstMethod(a); - var fooSymbol = (JMethodSymbol) foo.symbol(); + var fooSymbol = (JDTMethodSymbol) foo.symbol(); assertFalse(fooSymbol.isLambda()); assertThat(fooSymbol.toString()).isNotEmpty(); var barAssignment = (VariableTree) a.members().get(1); var initializer = (LambdaExpressionTree) barAssignment.initializer(); - var lambdaSymbol = (JMethodSymbol) initializer.symbol(); + var lambdaSymbol = (JDTMethodSymbol) initializer.symbol(); assertTrue(lambdaSymbol.isLambda()); assertThat(lambdaSymbol.toString()).isNotEmpty(); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserConfigTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JParserConfigTest.java similarity index 93% rename from java-frontend/src/test/java/org/sonar/java/model/JParserConfigTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JParserConfigTest.java index 78d488f74b7..adaeeed31c5 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserConfigTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JParserConfigTest.java @@ -14,16 +14,18 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Collections; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.event.Level; +import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JavaVersionImpl; import org.sonar.java.testing.ThreadLocalLogTester; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.java.model.JParserConfig.shouldEnablePreviewFlag; +import static org.sonar.java.jdt.JDTParserConfig.shouldEnablePreviewFlag; class JParserConfigTest { @RegisterExtension diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JParserReleasingJarsTest.java similarity index 97% rename from java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JParserReleasingJarsTest.java index d6c2207db62..ecfa2546b3b 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JParserReleasingJarsTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import com.google.common.io.Files; import java.io.File; @@ -23,6 +23,8 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JParserSemanticTest.java similarity index 96% rename from java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JParserSemanticTest.java index 326ef83dcf2..df5b59a01dc 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JParserSemanticTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.HashMap; import java.util.List; @@ -33,6 +33,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.sonar.java.model.AbstractTypedTree; +import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JUtils; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.KeywordSuper; +import org.sonar.java.model.KeywordThis; +import org.sonar.java.model.TypeParameterTreeImpl; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; @@ -163,7 +171,7 @@ void expression_qualified_name() { MemberSelectExpressionTreeImpl javaLangSystem = (MemberSelectExpressionTreeImpl) javaLangSystemOut.expression(); assertThat(typeOf(javaLangSystem)).isNotSameAs(Type.UNKNOWN); - assertThat(((JType) typeOf(javaLangSystem)).is("java.lang.System")) + assertThat(((JDTType) typeOf(javaLangSystem)).is("java.lang.System")) .isEqualTo(javaLangSystem.symbolType().is("java.lang.System")) .isTrue(); @@ -187,7 +195,7 @@ void expression_qualified_name() { .isNotNull() .isSameAs(typeOf(system).symbol()); assertThat(symbolOf(system).isTypeSymbol()).isTrue(); - assertThat(((JType) typeOf(system)).is("java.lang.System")) + assertThat(((JDTType) typeOf(system)).is("java.lang.System")) .isEqualTo(system.symbolType().is("java.lang.System")) .isTrue(); } @@ -206,7 +214,7 @@ void expression_this() { .isSameAs(typeOf(Objects.requireNonNull((ClassTreeImpl) keywordThis.symbolType().symbol().declaration()))) .isSameAs(typeOf(c)); assertThat(keywordThis.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).thisSymbol); + .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).thisSymbol); assertThat(keywordThis.symbol().isVariableSymbol()) .isTrue(); } @@ -230,7 +238,7 @@ void expression_this_qualified() { .isSameAs(typeOf(Objects.requireNonNull((ClassTreeImpl) keywordThis.symbolType().symbol().declaration()))) .isSameAs(typeOf(c1)); assertThat(keywordThis.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c1).symbol()).thisSymbol); + .isSameAs(((JDTTypeSymbol) typeOf(c1).symbol()).thisSymbol); assertThat(keywordThis.symbol().isVariableSymbol()) .isTrue(); } @@ -276,7 +284,7 @@ void expression_super_field_access() { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).superSymbol); + .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).superSymbol); IdentifierTreeImpl identifier = (IdentifierTreeImpl) expression.identifier(); assertThat(symbolOf(identifier)) @@ -312,7 +320,7 @@ void expression_super_field_access_qualified() { .isSameAs(Objects.requireNonNull((ClassTreeImpl) keywordSuper.symbolType().symbol().declaration())) .isSameAs(superClass); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(t).symbol()).superSymbol); + .isSameAs(((JDTTypeSymbol) typeOf(t).symbol()).superSymbol); IdentifierTreeImpl identifier = (IdentifierTreeImpl) expression.identifier(); assertThat(symbolOf(identifier)) @@ -480,7 +488,7 @@ class S { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).superSymbol); + .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).superSymbol); IdentifierTreeImpl identifier = (IdentifierTreeImpl) creationReference.method(); assertThat(symbolOf(identifier)) @@ -518,7 +526,7 @@ class S { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).superSymbol); + .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).superSymbol); IdentifierTreeImpl identifier = (IdentifierTreeImpl) creationReference.method(); assertThat(symbolOf(identifier)) @@ -674,7 +682,7 @@ void expression_super_method_invocation() { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).superSymbol); + .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).superSymbol); IdentifierTreeImpl i = (IdentifierTreeImpl) e2.identifier(); assertThat(symbolOf(i)) @@ -738,7 +746,7 @@ void expression_super_method_invocation_qualified() { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(t).symbol()).superSymbol); + .isSameAs(((JDTTypeSymbol) typeOf(t).symbol()).superSymbol); IdentifierTreeImpl identifier = (IdentifierTreeImpl) qualifiedMethodName.identifier(); assertThat(symbolOf(identifier)) @@ -789,10 +797,10 @@ class A { var lambda = (LambdaExpressionTree) f.initializer(); var p = (VariableTreeImpl) lambda.parameters().get(0); - Symbol newSymbol = ((JVariableSymbol) symbolOf(p)); + Symbol newSymbol = ((JDTVariableSymbol) symbolOf(p)); assertThat(newSymbol.declaration().firstToken().range().start().line()).isEqualTo(3); Symbol newOwner = newSymbol.owner(); - assertTrue(newOwner instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertTrue(newOwner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); } /** @@ -820,9 +828,9 @@ void foo() { VariableTreeImpl p = (VariableTreeImpl) lambda.parameters().get(0); // owner of lambda parameter is the field or variable to which it is assigned - Symbol newSymbol = ((JVariableSymbol) symbolOf(p)); + Symbol newSymbol = ((JDTVariableSymbol) symbolOf(p)); Symbol newOwner = newSymbol.owner(); - assertTrue(newOwner instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertTrue(newOwner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); } @Test @@ -850,7 +858,7 @@ void m() { LambdaExpressionTreeImpl l2 = (LambdaExpressionTreeImpl) r.expression(); assertThat(typeOf(l2)).isNotSameAs(Type.UNKNOWN); - assertThat(((JType) typeOf(l2)).is("java.util.function.Supplier")).isTrue(); + assertThat(((JDTType) typeOf(l2)).is("java.util.function.Supplier")).isTrue(); } /** @@ -876,15 +884,15 @@ void foo(Class[] arr1, Class[] arr2, boolean IdentifierTreeImpl c2 = (IdentifierTreeImpl) cond.falseExpression(); assertThat(typeOf(cond)).isNotSameAs(Type.UNKNOWN); - JType tcond = ((JType) typeOf(cond)); + JDTType tcond = ((JDTType) typeOf(cond)); assertThat(tcond.is("java.lang.Class[]")).isTrue(); assertThat(typeOf(c1)).isNotSameAs(Type.UNKNOWN); - JType tc1 = ((JType) typeOf(c1)); + JDTType tc1 = ((JDTType) typeOf(c1)); assertThat(tc1.is("java.lang.Class[]")).isTrue(); assertThat(typeOf(c2)).isNotSameAs(Type.UNKNOWN); - JType tc2 = ((JType) typeOf(c2)); + JDTType tc2 = ((JDTType) typeOf(c2)); assertThat(tc2.is("java.lang.Class[]")).isTrue(); assertThat(tc2) @@ -1118,7 +1126,7 @@ void declaration_sealed_class() { /** * {@link org.eclipse.jdt.core.dom.ITypeBinding#getTypeDeclaration()} should be used - * for {@link JSymbol#usages()} and {@link JSymbol#declaration()} + * for {@link JDTSymbol#usages()} and {@link JDTSymbol#declaration()} */ @Test void declaration_type_parameterized() { @@ -1130,10 +1138,10 @@ void declaration_type_parameterized() { assertThat(typeOf(c).symbol().usages()) .containsOnly(i); assertThat(typeOf(fieldType)).isNotSameAs(Type.UNKNOWN); - assertThat(((JTypeSymbol) typeOf(fieldType).symbol()).declaration()) + assertThat(((JDTTypeSymbol) typeOf(fieldType).symbol()).declaration()) .isSameAs(fieldType.symbolType().symbol().declaration()) .isSameAs(c); - assertThat(((JTypeSymbol) typeOf(fieldType).symbol()).usages()) + assertThat(((JDTTypeSymbol) typeOf(fieldType).symbol()).usages()) .containsExactlyElementsOf(fieldType.symbolType().symbol().usages()) .containsOnly(i); } @@ -1217,7 +1225,7 @@ void declaration_method() { /** * {@link org.eclipse.jdt.core.dom.IMethodBinding#getMethodDeclaration()} should be used - * for {@link JSymbol#usages()} and {@link JSymbol#declaration()} + * for {@link JDTSymbol#usages()} and {@link JDTSymbol#declaration()} */ @Test void declaration_method_parameterized() { @@ -1232,10 +1240,10 @@ void declaration_method_parameterized() { .containsOnly(i); assertThat(symbolOf(methodInvocation)) .isNotNull(); - assertThat(((JMethodSymbol) symbolOf(methodInvocation)).declaration()) + assertThat(((JDTMethodSymbol) symbolOf(methodInvocation)).declaration()) .isSameAs(methodInvocation.methodSymbol().declaration()) .isSameAs(m); - assertThat(((JMethodSymbol) symbolOf(methodInvocation)).usages()) + assertThat(((JDTMethodSymbol) symbolOf(methodInvocation)).usages()) .containsExactlyElementsOf(methodInvocation.methodSymbol().usages()) .containsOnly(i); } @@ -1439,7 +1447,7 @@ void type_try_with_resource() { VariableTreeImpl v = (VariableTreeImpl) s.resourceList().get(0); AbstractTypedTree t = (AbstractTypedTree) v.type(); assertThat(typeOf(t)).isNotSameAs(Type.UNKNOWN); - JType t2 = (JType) v.symbol().type(); + JDTType t2 = (JDTType) v.symbol().type(); assertThat(t2).isNotNull(); assertThat(t2.isUnknown()).isTrue(); assertThat(t2).isNotSameAs(Type.UNKNOWN); @@ -1579,10 +1587,10 @@ void annotation_on_type() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); VariableTreeImpl parameter = (VariableTreeImpl) m.parameters().get(0); - assertThat(((JVariableSymbol) symbolOf(parameter)).metadata().annotations().size()) + assertThat(((JDTVariableSymbol) symbolOf(parameter)).metadata().annotations().size()) .isEqualTo(parameter.symbol().metadata().annotations().size()) .isEqualTo(1); - assertThat(JUtils.parameterAnnotations(((JMethodSymbol) symbolOf(m)), 0).annotations().size()) + assertThat(JUtils.parameterAnnotations(((JDTMethodSymbol) symbolOf(m)), 0).annotations().size()) .isEqualTo(JUtils.parameterAnnotations(m.symbol(), 0).annotations().size()) .isEqualTo(1); } @@ -1653,8 +1661,8 @@ void statement_continue() { assertThat(l.labelSymbol) .isNotNull() .isSameAs(i.labelSymbol); - assertThat(l.labelSymbol.declaration) - .isSameAs(i.labelSymbol.declaration) + assertThat(l.labelSymbol.declaration()) + .isSameAs(i.labelSymbol.declaration()) .isSameAs(l); assertThat(l.labelSymbol.usages()) .containsExactlyElementsOf(l.symbol().usages()) @@ -1673,8 +1681,8 @@ void nested() { assertThat(l1.labelSymbol) .isNotNull() .isSameAs(i1.labelSymbol); - assertThat(l1.labelSymbol.declaration) - .isSameAs(i1.labelSymbol.declaration) + assertThat(l1.labelSymbol.declaration()) + .isSameAs(i1.labelSymbol.declaration()) .isSameAs(l1); assertThat(l1.labelSymbol.usages()) .containsOnly(i1); @@ -1689,8 +1697,8 @@ void nested() { assertThat(l2.labelSymbol) .isNotNull() .isSameAs(i2.labelSymbol); - assertThat(l2.labelSymbol.declaration) - .isSameAs(i2.labelSymbol.declaration) + assertThat(l2.labelSymbol.declaration()) + .isSameAs(i2.labelSymbol.declaration()) .isSameAs(l2); assertThat(l2.labelSymbol.usages()) .containsOnly(i2); @@ -1904,9 +1912,9 @@ int consume(C c) { assertNotEquals(symbol1, p2.symbol()); Symbol owner1 = symbol1.owner(); - assertTrue(owner1 instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertTrue(owner1 instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); Symbol owner2 = symbol2.owner(); - assertTrue(owner2 instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertTrue(owner2 instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); assertNotEquals(owner1, owner2); } @@ -1931,7 +1939,7 @@ class C { Symbol jOwner = j.symbol().owner(); Symbol kOwner = k.symbol().owner(); assertNotEquals(jOwner, kOwner); - assertTrue(iOwner instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertTrue(iOwner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); } @Test @@ -1976,12 +1984,12 @@ interface F extends java.util.function.Consumer {} var k = (VariableTree) classTree.members().get(0); Symbol s1 = ((LambdaExpressionTree) k.initializer()).parameters().get(0).symbol(); Symbol s1Owner = s1.owner(); - assertThat(s1Owner instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); + assertThat(s1Owner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); var j = (VariableTree) classTree.members().get(1); Symbol s2 = ((LambdaExpressionTree) j.initializer()).parameters().get(0).symbol(); Symbol s2Owner = s2.owner(); - assertThat(s2Owner instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); + assertThat(s2Owner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); assertNotEquals(s1Owner, s2Owner); // For sanity, check that the symbols are not equal @@ -2023,9 +2031,9 @@ void f2() { Symbol ownerC = pOfC.symbol().owner(); Symbol ownerB = pOfB.symbol().owner(); - assertThat(ownerA instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); - assertThat(ownerB instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); - assertThat(ownerC instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); + assertThat(ownerA instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); + assertThat(ownerB instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); + assertThat(ownerC instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); assertNotEquals(ownerA, ownerC); assertNotEquals(ownerA, ownerB); assertNotEquals(ownerC, ownerB); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JParserTest.java similarity index 93% rename from java-frontend/src/test/java/org/sonar/java/model/JParserTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JParserTest.java index d6d8a3af6a1..533a83fd62f 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JParserTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import com.sonar.sslr.api.RecognitionException; import java.io.BufferedInputStream; @@ -57,6 +57,12 @@ import org.sonar.api.batch.fs.InputFile; import org.sonar.java.AnalysisProgress; import org.sonar.java.TestUtils; +import org.sonar.java.model.InternalSyntaxToken; +import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JParserConfig.Result; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.JavaVersionImpl; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.JavaVersion; @@ -98,8 +104,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.sonar.java.model.JParser.convertTokenTypeToCommentKind; -import static org.sonar.java.model.JParser.isComment; +import static org.sonar.java.jdt.JDTParser.convertTokenTypeToCommentKind; +import static org.sonar.java.jdt.JDTParser.isComment; import static org.sonar.java.model.JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; import static org.sonar.java.model.JParserConfig.Mode.BATCH; import static org.sonar.java.model.JParserConfig.Mode.FILE_BY_FILE; @@ -130,8 +136,8 @@ void should_throw_RecognitionException_in_case_of_syntax_error() { void should_recover_if_parser_fails() { String version = "12"; List classpath = Collections.singletonList(new File("unknownFile")); - ASTParser astParser = FILE_BY_FILE.create(JavaVersionImpl.fromString(version), classpath).astParser(); - assertThrows(RecognitionException.class, () -> JParser.parse(astParser, version, "A", "class A { }")); + ASTParser astParser = ((JDTParserConfig) FILE_BY_FILE.create(JavaVersionImpl.fromString(version), classpath)).jdtParser(); + assertThrows(RecognitionException.class, () -> JDTParser.parse(astParser, version, "A", "class A { }")); } @Test @@ -165,7 +171,7 @@ void warnings_are_collected() { @Test void should_rethrow_when_consumer_throws() { RuntimeException expected = new RuntimeException(); - BiConsumer consumer = (inputFile, result) -> { + BiConsumer consumer = (inputFile, result) -> { throw expected; }; InputFile inputFile = mock(InputFile.class); @@ -183,8 +189,8 @@ void should_rethrow_when_consumer_throws() { @Test void consumer_should_receive_exceptions_thrown_during_parsing() throws Exception { - List results = new ArrayList<>(); - BiConsumer consumer = (inputFile, result) -> results.add(result); + List results = new ArrayList<>(); + BiConsumer consumer = (inputFile, result) -> results.add(result); InputFile inputFile = mock(InputFile.class); doReturn("/tmp/Example.java") @@ -196,14 +202,14 @@ void consumer_should_receive_exceptions_thrown_during_parsing() throws Exception .create(MAXIMUM_SUPPORTED_JAVA_VERSION, Collections.emptyList()) .parse(Collections.singleton(inputFile), () -> false, new AnalysisProgress(1), consumer); - JParserConfig.Result result = results.get(0); + Result result = results.get(0); assertThrows(IOException.class, result::get); } @Test void consumer_should_receive_exceptions_thrown_during_parsing_as_batch() throws Exception { - List results = new ArrayList<>(); - BiConsumer consumer = (inputFile, result) -> results.add(result); + List results = new ArrayList<>(); + BiConsumer consumer = (inputFile, result) -> results.add(result); InputFile inputFile = spy(TestUtils.inputFile("src/test/files/metrics/Classes.java")); when(inputFile.contents()).thenThrow(IOException.class); @@ -211,14 +217,14 @@ void consumer_should_receive_exceptions_thrown_during_parsing_as_batch() throws .create(MAXIMUM_SUPPORTED_JAVA_VERSION, Collections.emptyList()) .parse(Collections.singleton(inputFile), () -> false, new AnalysisProgress(1), consumer); - JParserConfig.Result result = results.get(0); + Result result = results.get(0); assertThrows(IOException.class, result::get); } @Test void should_propagate_exception_raised_by_batch_parsing() { NullPointerException expected = new NullPointerException(""); - BiConsumer consumerThrowing = (inputFile, result) -> { + BiConsumer consumerThrowing = (inputFile, result) -> { throw expected; }; List inputFiles = Collections.singletonList(TestUtils.inputFile("src/test/files/metrics/Classes.java")); @@ -515,12 +521,12 @@ void test_first_index_of_tokens_in_eclipse_ast() { astParser.setUnitName(unitName); astParser.setSource(source.toCharArray()); CompilationUnit compilationUnit = (CompilationUnit) astParser.createAST(null); - TokenManager tokenManager = JParser.createTokenManager(version, unitName, source); + TokenManager tokenManager = JDTParser.createTokenManager(version, unitName, source); - assertThat(JParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameIdentifier, TerminalToken.TokenNameLBRACE)).isEqualTo(1); - assertThat(JParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameLBRACE, TerminalToken.TokenNameIdentifier)).isEqualTo(1); - assertThat(JParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameRBRACE, TerminalToken.TokenNameLBRACE)).isEqualTo(2); - assertThatThrownBy(() -> JParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNamebreak, TerminalToken.TokenNameconst)) + assertThat(JDTParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameIdentifier, TerminalToken.TokenNameLBRACE)).isEqualTo(1); + assertThat(JDTParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameLBRACE, TerminalToken.TokenNameIdentifier)).isEqualTo(1); + assertThat(JDTParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameRBRACE, TerminalToken.TokenNameLBRACE)).isEqualTo(2); + assertThatThrownBy(() -> JDTParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNamebreak, TerminalToken.TokenNameconst)) .isInstanceOf(IllegalStateException.class) .hasMessage("Failed to find token TokenNamebreak or TokenNameconst in the tokens of a org.eclipse.jdt.core.dom.CompilationUnit"); } @@ -541,7 +547,7 @@ class A { void foo() {} } """; - TokenManager tokens = JParser.createTokenManager(version, unitName, source); + TokenManager tokens = JDTParser.createTokenManager(version, unitName, source); assertThat(tokens.size()).isEqualTo(15); @@ -636,7 +642,7 @@ void dont_include_running_VM_Bootclasspath_if_jvm_jrt_fs_jar_already_provided_in void test_parse_file_by_file() throws Exception { List inputFiles = Arrays.asList(TestUtils.inputFile("src/test/files/metrics/Classes.java"), TestUtils.inputFile("src/test/files/metrics/Methods.java")); - List results = new ArrayList<>(); + List results = new ArrayList<>(); List inputFilesProcessed = new ArrayList<>(); FILE_BY_FILE .create(MAXIMUM_SUPPORTED_JAVA_VERSION, DEFAULT_CLASSPATH) @@ -652,7 +658,7 @@ void test_parse_file_by_file() throws Exception { void test_parse_as_batch() throws Exception { List inputFiles = Arrays.asList(TestUtils.inputFile("src/test/files/metrics/Classes.java"), TestUtils.inputFile("src/test/files/metrics/Methods.java")); - List results = new ArrayList<>(); + List results = new ArrayList<>(); List inputFilesProcessed = new ArrayList<>(); BATCH .create(MAXIMUM_SUPPORTED_JAVA_VERSION, DEFAULT_CLASSPATH) @@ -670,7 +676,7 @@ void failing_batch_mode_should_continue_file_by_file() { TestUtils.inputFile("src/test/files/metrics/Classes.java"), TestUtils.inputFile("src/test/files/metrics/Methods.java")); List trace = new ArrayList<>(); - BiConsumer action = (inputFile, result) -> { + BiConsumer action = (inputFile, result) -> { trace.addAll(logTester.logs()); logTester.clear(); try { @@ -698,12 +704,12 @@ void successful_batch_mode_with_missing_analyzed_files_should_continue_file_by_f List inputFiles = Arrays.asList( TestUtils.inputFile("src/test/files/metrics/Classes.java"), TestUtils.inputFile("src/test/files/metrics/Methods.java")); - BiConsumer doNothingAction = (inputFile, result) -> {}; - JParserConfig config = spy(JParserConfig.Mode.BATCH + BiConsumer doNothingAction = (inputFile, result) -> {}; + JDTParserConfig config = spy((JDTParserConfig) JParserConfig.Mode.BATCH .create(MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), false)); // Return a lazy ASTParser that do nothing to ensure that we have not analyzed files ASTParser astParser = mock(ASTParser.class); - when(config.astParser()).thenReturn(astParser); + when(config.jdtParser()).thenReturn(astParser); config.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), doNothingAction); @@ -720,7 +726,7 @@ void failing_batch_mode_should_stop_file_by_file_when_cancelled() { TestUtils.inputFile("src/test/files/metrics/Methods.java")); List trace = new ArrayList<>(); AtomicBoolean isCanceled = new AtomicBoolean(false); - BiConsumer action = (inputFile, result) -> { + BiConsumer action = (inputFile, result) -> { trace.addAll(logTester.logs()); logTester.clear(); try { @@ -807,7 +813,7 @@ public static List tokens(JavaTree tree) { void failing_batch_mode_with_empty_file_list_should_not_continue_file_by_file() { List inputFiles = List.of(); List trace = new ArrayList<>(); - BiConsumer action = (inputFile, result) -> trace.add("should not be called "); + BiConsumer action = (inputFile, result) -> trace.add("should not be called "); BatchWithException batchWithException = new BatchWithException(); // exception before the first file batchWithException.exceptions.push(new NullPointerException("Boom!")); @@ -818,7 +824,7 @@ void failing_batch_mode_with_empty_file_list_should_not_continue_file_by_file() "Unexpected NullPointerException: Boom!"); } - static class BatchWithException extends JParserConfig.Batch { + static class BatchWithException extends JDTParserConfig.Batch { Deque exceptions = new LinkedList<>(); @@ -827,16 +833,16 @@ public BatchWithException() { } @Override - public ASTParser astParser() { + public ASTParser jdtParser() { if (!exceptions.isEmpty()) { throw exceptions.pop(); } - return super.astParser(); + return super.jdtParser(); } } - private void assertResultsOfParsing(List results, List inputFilesProcessed) throws Exception { + private void assertResultsOfParsing(List results, List inputFilesProcessed) throws Exception { assertThat(inputFilesProcessed).hasSize(2); assertThat(inputFilesProcessed.get(0).filename()).isEqualTo("Classes.java"); assertThat(inputFilesProcessed.get(1).filename()).isEqualTo("Methods.java"); @@ -855,18 +861,8 @@ private void assertResultsOfParsing(List results, List inputFiles = Arrays.asList(TestUtils.inputFile("src/test/files/metrics/Classes.java"), TestUtils.inputFile("src/test/files/metrics/Methods.java")); - BiConsumer action = spy(new BiConsumer() { - @Override - public void accept(InputFile inputFile, JParserConfig.Result result) { - // Do nothing - } - }); - BooleanSupplier isCanceled = spy(new BooleanSupplier() { - @Override - public boolean getAsBoolean() { - return false; - } - }); + BiConsumer action = mock(BiConsumer.class); + BooleanSupplier isCanceled = mock(BooleanSupplier.class); FILE_BY_FILE .create(MAXIMUM_SUPPORTED_JAVA_VERSION, DEFAULT_CLASSPATH) @@ -883,7 +879,7 @@ public boolean getAsBoolean() { void test_is_canceled_is_called_file_by_file() { List inputFiles = Arrays.asList(TestUtils.inputFile("src/test/files/metrics/Classes.java"), TestUtils.inputFile("src/test/files/metrics/Methods.java")); - List results = new ArrayList<>(); + List results = new ArrayList<>(); FILE_BY_FILE .create(MAXIMUM_SUPPORTED_JAVA_VERSION, DEFAULT_CLASSPATH) .parse(inputFiles, () -> true, new AnalysisProgress(inputFiles.size()), (inputFile, result) -> results.add(result)); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JProblemTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JProblemTest.java similarity index 58% rename from java-frontend/src/test/java/org/sonar/java/model/JProblemTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JProblemTest.java index 43a50ba5c47..8dc08131ebb 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JProblemTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JProblemTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.HashSet; import java.util.List; @@ -27,9 +27,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -38,22 +37,22 @@ class JProblemTest { @Test void normalizes_preview_messages_at_construction() { String message = "Preview feature used Use --enable-preview to enable"; - JProblem preview = new JProblem(message, ProblemKind.PREVIEW_FEATURE_USED); + JDTProblem preview = new JDTProblem(message, ProblemKind.PREVIEW_FEATURE_USED); assertThat(preview.message()).isEqualTo("Preview feature used"); assertThat(preview).hasToString(preview.message()); - assertThat(new JProblem("Preview feature used", ProblemKind.PREVIEW_FEATURE_USED).message()).isEqualTo("Preview feature used"); - assertThat(new JProblem(message + ".", ProblemKind.PREVIEW_FEATURE_USED).message()).isEqualTo(message + "."); + assertThat(new JDTProblem("Preview feature used", ProblemKind.PREVIEW_FEATURE_USED).message()).isEqualTo("Preview feature used"); + assertThat(new JDTProblem(message + ".", ProblemKind.PREVIEW_FEATURE_USED).message()).isEqualTo(message + "."); for (ProblemKind kind : ProblemKind.values()) { if (kind != ProblemKind.PREVIEW_FEATURE_USED) { - assertThat(new JProblem(message, kind).message()).isEqualTo(message); + assertThat(new JDTProblem(message, kind).message()).isEqualTo(message); } } } @Test void equivalent_normalized_messages_have_equal_values_and_hashes() { - JProblem withGuidance = new JProblem("Preview feature used Use --enable-preview to enable", ProblemKind.PREVIEW_FEATURE_USED); - JProblem withoutGuidance = new JProblem("Preview feature used", ProblemKind.PREVIEW_FEATURE_USED); + JDTProblem withGuidance = new JDTProblem("Preview feature used Use --enable-preview to enable", ProblemKind.PREVIEW_FEATURE_USED); + JDTProblem withoutGuidance = new JDTProblem("Preview feature used", ProblemKind.PREVIEW_FEATURE_USED); assertThat(withGuidance).isEqualTo(withoutGuidance); assertThat(withoutGuidance).isEqualTo(withGuidance); assertThat(withGuidance).hasSameHashCodeAs(withoutGuidance); @@ -62,12 +61,12 @@ void equivalent_normalized_messages_have_equal_values_and_hashes() { @Test void compiler_options_enable_only_supported_warnings() { - assertThat(JProblem.compilerOptions()).containsExactlyInAnyOrder( + assertThat(JDTProblem.compilerOptions()).containsExactlyInAnyOrder( JavaCore.COMPILER_PB_UNUSED_IMPORT, JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK, JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT, JavaCore.COMPILER_PB_HIDDEN_CATCH_BLOCK); - Set options = JProblem.compilerOptions(); + Set options = JDTProblem.compilerOptions(); assertThatThrownBy(options::clear).isInstanceOf(UnsupportedOperationException.class); } @@ -77,39 +76,39 @@ void classifies_ecj_syntax_errors_and_semantic_problems() { IProblem toleratedSwitch = problem(IProblem.SwitchExpressionsYieldMissingDefaultCase, "Switch"); IProblem undefined = problem(IProblem.UndefinedType, "Unknown type"); IProblem preview = problem(IProblem.PreviewFeatureDisabled, "Preview"); - assertThat(JProblem.isSyntaxError(syntax)).isTrue(); - assertThat(JProblem.isSyntaxError(toleratedSwitch)).isFalse(); - assertThat(JProblem.isSyntaxError(undefined)).isFalse(); - assertThat(JProblem.semanticProblems(List.of(syntax, undefined, preview))) - .containsExactlyInAnyOrder(new JProblem("Unknown type", ProblemKind.UNDEFINED_TYPE), new JProblem("Preview", ProblemKind.PREVIEW_FEATURE_USED)); - assertThat(JProblem.kindOf(syntax)).isNull(); + assertThat(JDTProblem.isSyntaxError(syntax)).isTrue(); + assertThat(JDTProblem.isSyntaxError(toleratedSwitch)).isFalse(); + assertThat(JDTProblem.isSyntaxError(undefined)).isFalse(); + assertThat(JDTProblem.semanticProblems(List.of(syntax, undefined, preview))) + .containsExactlyInAnyOrder(new JDTProblem("Unknown type", ProblemKind.UNDEFINED_TYPE), new JDTProblem("Preview", ProblemKind.PREVIEW_FEATURE_USED)); + assertThat(JDTProblem.kindOf(syntax)).isNull(); } @Test void test_equals() { - JProblem p1 = new JProblem("message1", ProblemKind.UNDEFINED_TYPE); - assertFalse(p1.equals(null)); - assertTrue(p1.equals(p1)); + JDTProblem p1 = new JDTProblem("message1", ProblemKind.UNDEFINED_TYPE); + assertNotNull(p1); + assertEquals(p1, p1); - JProblem p2 = new JProblem("message1", ProblemKind.UNDEFINED_TYPE); - assertTrue(p1.equals(p2)); - p2 = new JProblem("message2", ProblemKind.UNDEFINED_TYPE); - assertFalse(p1.equals(p2)); - p2 = new JProblem("message1", ProblemKind.PREVIEW_FEATURE_USED); - assertFalse(p1.equals(p2)); + JDTProblem p2 = new JDTProblem("message1", ProblemKind.UNDEFINED_TYPE); + assertEquals(p1, p2); + p2 = new JDTProblem("message2", ProblemKind.UNDEFINED_TYPE); + assertNotEquals(p1, p2); + p2 = new JDTProblem("message1", ProblemKind.PREVIEW_FEATURE_USED); + assertNotEquals(p1, p2); } @Test void test_hashcode() { assertEquals( - new JProblem("a", ProblemKind.UNDEFINED_TYPE).hashCode(), - new JProblem("a", ProblemKind.UNDEFINED_TYPE).hashCode()); + new JDTProblem("a", ProblemKind.UNDEFINED_TYPE).hashCode(), + new JDTProblem("a", ProblemKind.UNDEFINED_TYPE).hashCode()); assertNotEquals( - new JProblem("a", ProblemKind.UNDEFINED_TYPE).hashCode(), - new JProblem("b", ProblemKind.UNDEFINED_TYPE).hashCode()); + new JDTProblem("a", ProblemKind.UNDEFINED_TYPE).hashCode(), + new JDTProblem("b", ProblemKind.UNDEFINED_TYPE).hashCode()); assertNotEquals( - new JProblem("a", ProblemKind.UNDEFINED_TYPE).hashCode(), - new JProblem("a", ProblemKind.PREVIEW_FEATURE_USED).hashCode()); + new JDTProblem("a", ProblemKind.UNDEFINED_TYPE).hashCode(), + new JDTProblem("a", ProblemKind.PREVIEW_FEATURE_USED).hashCode()); } private static IProblem problem(int id, String message) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JSemaTest.java similarity index 95% rename from java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JSemaTest.java index ed51a20a6a9..932662fb0c0 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JSemaTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Objects; import org.eclipse.jdt.core.JavaCore; @@ -23,6 +23,7 @@ import org.eclipse.jdt.core.dom.ITypeBinding; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.sonar.java.model.JParserConfig; import org.sonar.plugins.java.api.semantic.Type; import static org.assertj.core.api.Assertions.assertThat; @@ -73,7 +74,7 @@ void resolvePackageAnnotations() { .hasSize(1); } - private JSema sema; + private JDTSema sema; @BeforeEach void setup() { @@ -90,7 +91,7 @@ void setup() { astParser.setUnitName("File.java"); astParser.setSource("".toCharArray()); AST ast = astParser.createAST(null).getAST(); - sema = new JSema(ast); + sema = new JDTSema(ast); } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JSymbolMetadataTest.java similarity index 93% rename from java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JSymbolMetadataTest.java index 7198d5176f4..d41a8a2f23d 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JSymbolMetadataTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.io.IOException; import java.nio.file.Path; @@ -26,6 +26,9 @@ import javax.annotation.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.JNullabilityData; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.expression.MethodInvocationTreeImpl; @@ -69,10 +72,10 @@ class JSymbolMetadataTest { void should_convert_annotation_values() { JavaTree.CompilationUnitTreeImpl cu = test("import java.lang.annotation.*; @Target({ElementType.TYPE, ElementType.METHOD}) @interface A { }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); - Object[] value = (Object[]) ((JTypeSymbol) typeOf(c).symbol()).metadata().annotations().get(0).values().get(0).value(); + Object[] value = (Object[]) ((JDTTypeSymbol) typeOf(c).symbol()).metadata().annotations().get(0).values().get(0).value(); assertThat(value).hasSize(2); - assertThat(((JVariableSymbol) value[0]).isEnum()).isTrue(); - assertThat(((JVariableSymbol) value[1]).isEnum()).isTrue(); + assertThat(((JDTVariableSymbol) value[0]).isEnum()).isTrue(); + assertThat(((JDTVariableSymbol) value[1]).isEnum()).isTrue(); } @Test @@ -244,7 +247,7 @@ class NullabilityDataTest { @Test void nullability_data_non_null() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(NON_NULL, VARIABLE, null, null, false); + new JNullabilityData(NON_NULL, VARIABLE, null, null, false); assertThat(nonNullAtVariable.isNonNull(VARIABLE, true, false)).isTrue(); assertThat(nonNullAtVariable.isStrongNullable(VARIABLE, true, false)).isFalse(); @@ -254,7 +257,7 @@ void nullability_data_non_null() { @Test void nullability_data_weak_nullable() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(WEAK_NULLABLE, VARIABLE, null, null, false); + new JNullabilityData(WEAK_NULLABLE, VARIABLE, null, null, false); assertThat(nonNullAtVariable.isNonNull(VARIABLE, true, false)).isFalse(); assertThat(nonNullAtVariable.isStrongNullable(VARIABLE, true, false)).isFalse(); @@ -264,7 +267,7 @@ void nullability_data_weak_nullable() { @Test void nullability_data_strong_nullable() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(STRONG_NULLABLE, VARIABLE, null, null, false); + new JNullabilityData(STRONG_NULLABLE, VARIABLE, null, null, false); assertThat(nonNullAtVariable.isNonNull(VARIABLE, true, false)).isFalse(); assertThat(nonNullAtVariable.isStrongNullable(VARIABLE, true, false)).isTrue(); @@ -275,7 +278,7 @@ void nullability_data_strong_nullable() { @Test void nullability_data_level_variable() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(NON_NULL, VARIABLE, null, null, false); + new JNullabilityData(NON_NULL, VARIABLE, null, null, false); assertThat(nonNullAtVariable.isNonNull(VARIABLE, true, false)).isTrue(); assertThat(nonNullAtVariable.isNonNull(METHOD, true, false)).isTrue(); @@ -286,7 +289,7 @@ void nullability_data_level_variable() { @Test void nullability_data_level_method() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(NON_NULL, METHOD, null, null, false); + new JNullabilityData(NON_NULL, METHOD, null, null, false); assertThat(nonNullAtVariable.isNonNull(VARIABLE, true, false)).isFalse(); assertThat(nonNullAtVariable.isNonNull(METHOD, true, false)).isTrue(); @@ -297,7 +300,7 @@ void nullability_data_level_method() { @Test void nullability_data_level_class() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(NON_NULL, CLASS, null, null, false); + new JNullabilityData(NON_NULL, CLASS, null, null, false); assertThat(nonNullAtVariable.isNonNull(VARIABLE, true, false)).isFalse(); assertThat(nonNullAtVariable.isNonNull(METHOD, true, false)).isFalse(); @@ -308,7 +311,7 @@ void nullability_data_level_class() { @Test void nullability_data_level_package() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(NON_NULL, PACKAGE, null, null, false); + new JNullabilityData(NON_NULL, PACKAGE, null, null, false); assertThat(nonNullAtVariable.isNonNull(VARIABLE, true, false)).isFalse(); assertThat(nonNullAtVariable.isNonNull(METHOD, true, false)).isFalse(); @@ -319,7 +322,7 @@ void nullability_data_level_package() { @Test void nullability_data_ignore_meta_annotation() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(NON_NULL, VARIABLE, null, null, true); + new JNullabilityData(NON_NULL, VARIABLE, null, null, true); assertThat(nonNullAtVariable.isNonNull(VARIABLE, true, false)).isFalse(); assertThat(nonNullAtVariable.isNonNull(METHOD, true, false)).isFalse(); @@ -330,7 +333,7 @@ void nullability_data_ignore_meta_annotation() { @Test void nullability_data_do_not_ignore_meta_annotation() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(NON_NULL, CLASS, null, null, true); + new JNullabilityData(NON_NULL, CLASS, null, null, true); assertThat(nonNullAtVariable.isNonNull(VARIABLE, false, false)).isFalse(); assertThat(nonNullAtVariable.isNonNull(METHOD, false, false)).isFalse(); @@ -341,7 +344,7 @@ void nullability_data_do_not_ignore_meta_annotation() { @Test void nullability_unknown_type() { SymbolMetadata.NullabilityData nonNullAtVariable = - new JSymbolMetadata.JNullabilityData(UNKNOWN, VARIABLE, null, null, false); + new JNullabilityData(UNKNOWN, VARIABLE, null, null, false); assertThat(nonNullAtVariable.isStrongNullable(VARIABLE, true, false)).isFalse(); assertThat(nonNullAtVariable.isNullable(VARIABLE, true, false)).isFalse(); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JSymbolTest.java similarity index 82% rename from java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JSymbolTest.java index ccdd4d1e5a5..d6e922af002 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JSymbolTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; @@ -24,6 +24,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.KeywordSuper; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.EnumConstantTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; @@ -51,9 +54,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.sonar.java.model.JParserTestUtils.sema; +import static org.sonar.java.jdt.JdtTestUtils.sema; +import static org.sonar.java.jdt.JdtTestUtils.typeBinding; import static org.sonar.java.model.JParserTestUtils.symbolOf; -import static org.sonar.java.model.JParserTestUtils.typeBinding; import static org.sonar.java.model.JParserTestUtils.typeOf; import static org.sonar.java.model.assertions.SymbolAssert.assertThat; @@ -66,13 +69,13 @@ void name() { MethodTreeImpl constructor = (MethodTreeImpl) c.members().get(0); MethodTreeImpl method = (MethodTreeImpl) c.members().get(1); - assertThat(((JTypeSymbol) typeOf(c).symbol())) + assertThat(((JDTTypeSymbol) typeOf(c).symbol())) .hasSameNameAs(c.symbol()) .hasName("C"); - assertThat(((JMethodSymbol) symbolOf(constructor))) + assertThat(((JDTMethodSymbol) symbolOf(constructor))) .hasSameNameAs(constructor.symbol()) .hasName(""); - assertThat(((JMethodSymbol) symbolOf(method))) + assertThat(((JDTMethodSymbol) symbolOf(method))) .hasSameNameAs(method.symbol()) .hasName("m"); } @@ -88,31 +91,31 @@ void owner() { ClassTreeImpl c3 = (ClassTreeImpl) m.block().body().get(0); VariableTreeImpl u = (VariableTreeImpl) c3.members().get(0); - assertThat(((JTypeSymbol) typeOf(c1).symbol())) + assertThat(((JDTTypeSymbol) typeOf(c1).symbol())) .as("of top-level class") .hasOwner(sema(cu).packageSymbol(typeBinding(c1).getPackage())); - assertThat(((JTypeSymbol) typeOf(c2).symbol())) + assertThat(((JDTTypeSymbol) typeOf(c2).symbol())) .as("of nested class") - .hasOwner(((JTypeSymbol) typeOf(c1).symbol())); + .hasOwner(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JTypeSymbol) typeOf(c3).symbol())) + assertThat(((JDTTypeSymbol) typeOf(c3).symbol())) .as("of local class") - .hasOwner(((JMethodSymbol) symbolOf(m))); + .hasOwner(((JDTMethodSymbol) symbolOf(m))); - assertThat(((JMethodSymbol) symbolOf(m))) + assertThat(((JDTMethodSymbol) symbolOf(m))) .as("of method") - .hasOwner(((JTypeSymbol) typeOf(c1).symbol())); + .hasOwner(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JVariableSymbol) symbolOf(f))) + assertThat(((JDTVariableSymbol) symbolOf(f))) .as("of field") - .hasOwner(((JTypeSymbol) typeOf(c1).symbol())); + .hasOwner(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JVariableSymbol) symbolOf(p))) + assertThat(((JDTVariableSymbol) symbolOf(p))) .as("of method parameter") - .hasOwner(((JMethodSymbol) symbolOf(m))); + .hasOwner(((JDTMethodSymbol) symbolOf(m))); - assertThat(((JTypeSymbol) symbolOf(p).type().symbol())) + assertThat(((JDTTypeSymbol) symbolOf(p).type().symbol())) .as("of type int") .hasOwner(Symbol.ROOT_PACKAGE) .hasSameHashCodeAs(p.type().symbolType().symbol().hashCode()); @@ -120,7 +123,7 @@ void owner() { assertThat(sema(cu).packageSymbol(null)) .isEqualTo(Symbol.ROOT_PACKAGE); - JType uType = ((JType) symbolOf(u).type()); + JDTType uType = ((JDTType) symbolOf(u).type()); Symbol.TypeSymbol uTypeSymbol = uType.symbol(); assertThat(uType.isUnknown()).isTrue(); assertThat(uTypeSymbol.isUnknown()).isTrue(); @@ -165,13 +168,13 @@ void owner_local_record() { ClassTreeImpl r = (ClassTreeImpl) m.block().body().get(0); VariableTreeImpl p = (VariableTreeImpl) r.recordComponents().get(0); - assertThat(((JVariableSymbol) symbolOf(p))) + assertThat(((JDTVariableSymbol) symbolOf(p))) .as("of record component") - .hasOwner(((JTypeSymbol) typeOf(r).symbol())); + .hasOwner(((JDTTypeSymbol) typeOf(r).symbol())); - assertThat(((JMethodSymbol) symbolOf(m))) + assertThat(((JDTMethodSymbol) symbolOf(m))) .as("of method") - .hasOwner(((JTypeSymbol) typeOf(c1).symbol())); + .hasOwner(((JDTTypeSymbol) typeOf(c1).symbol())); } @Test @@ -186,7 +189,7 @@ class C { var lambda = (LambdaExpressionTree) ((VariableTree) classTree.members().get(0)).initializer(); var i = (IdentifierTree) lambda.body(); Symbol owner = i.symbol().owner(); - assertTrue(owner instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertTrue(owner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); } @Test @@ -205,7 +208,7 @@ void foo() { LambdaExpressionTree lambda = (LambdaExpressionTree) bindingInsideMethod.initializer(); VariableTree l = lambda.parameters().get(0); Symbol owner = l.symbol().owner(); - assertTrue(owner instanceof JMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertTrue(owner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); } @Test @@ -214,8 +217,8 @@ void primitive_type_hash_code() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); VariableTreeImpl uField = (VariableTreeImpl) c.members().get(0); VariableTreeImpl vField = (VariableTreeImpl) c.members().get(1); - assertThat(((JTypeSymbol) symbolOf(uField).type().symbol())) - .hasSameHashCodeAs(((JTypeSymbol) symbolOf(vField).type().symbol())); + assertThat(((JDTTypeSymbol) symbolOf(uField).type().symbol())) + .hasSameHashCodeAs(((JDTTypeSymbol) symbolOf(vField).type().symbol())); } @Test @@ -228,29 +231,29 @@ void enclosingClass() { VariableTreeImpl p = (VariableTreeImpl) m.parameters().get(0); ClassTreeImpl c3 = (ClassTreeImpl) m.block().body().get(0); - assertThat(((JTypeSymbol) typeOf(c1).symbol())) + assertThat(((JDTTypeSymbol) typeOf(c1).symbol())) .as("of top-level class") - .hasEnclosingClass(((JTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JTypeSymbol) typeOf(c2).symbol())) + assertThat(((JDTTypeSymbol) typeOf(c2).symbol())) .as("of nested class") - .hasEnclosingClass(((JTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JTypeSymbol) typeOf(c3).symbol())) + assertThat(((JDTTypeSymbol) typeOf(c3).symbol())) .as("of local class") - .hasEnclosingClass(((JTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JMethodSymbol) symbolOf(m))) + assertThat(((JDTMethodSymbol) symbolOf(m))) .as("of method") - .hasEnclosingClass(((JTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JVariableSymbol) symbolOf(f))) + assertThat(((JDTVariableSymbol) symbolOf(f))) .as("of field") - .hasEnclosingClass(((JTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JVariableSymbol) symbolOf(p))) + assertThat(((JDTVariableSymbol) symbolOf(p))) .as("of method parameter") - .hasEnclosingClass(((JTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); } @Test @@ -261,13 +264,13 @@ void record_enclosingClass() { ClassTreeImpl c2 = (ClassTreeImpl) c1.members().get(0); VariableTreeImpl p = (VariableTreeImpl) c2.recordComponents().get(0); - assertThat(((JVariableSymbol) symbolOf(f))) + assertThat(((JDTVariableSymbol) symbolOf(f))) .as("of field") - .hasEnclosingClass(((JTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); - assertThat(((JVariableSymbol) symbolOf(p))) + assertThat(((JDTVariableSymbol) symbolOf(p))) .as("of method parameter") - .hasEnclosingClass(((JTypeSymbol) typeOf(c2).symbol())); + .hasEnclosingClass(((JDTTypeSymbol) typeOf(c2).symbol())); } @Test @@ -294,16 +297,16 @@ private void variable_in_class_initializer(boolean isStatic) { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); BlockTreeImpl b = (BlockTreeImpl) c.members().get(1); VariableTreeImpl v = (VariableTreeImpl) b.body().get(0); - Symbol.TypeSymbol t = ((JTypeSymbol) typeOf(c).symbol()); + Symbol.TypeSymbol t = ((JDTTypeSymbol) typeOf(c).symbol()); - JVariableSymbol variableSymbol = ((JVariableSymbol) symbolOf(v)); + JDTVariableSymbol variableSymbol = ((JDTVariableSymbol) symbolOf(v)); assertThat(variableSymbol).isSameAs(v.symbol()); - JInitializerBlockSymbol initializerBlock = (JInitializerBlockSymbol) variableSymbol.owner(); + JDTInitializerBlockSymbol initializerBlock = (JDTInitializerBlockSymbol) variableSymbol.owner(); if (isStatic) { - assertThat(initializerBlock).isSameAs(sema(cu).staticInitializerBlockSymbol((JTypeSymbol) c.symbol())); + assertThat(initializerBlock).isSameAs(sema(cu).staticInitializerBlockSymbol((JDTTypeSymbol) c.symbol())); } else { - assertThat(initializerBlock).isSameAs(sema(cu).initializerBlockSymbol((JTypeSymbol) c.symbol())); + assertThat(initializerBlock).isSameAs(sema(cu).initializerBlockSymbol((JDTTypeSymbol) c.symbol())); } assertThat(variableSymbol).hasEnclosingClass(t); @@ -361,17 +364,17 @@ void type() { VariableTreeImpl field = (VariableTreeImpl) c.members().get(0); MethodTreeImpl method = (MethodTreeImpl) c.members().get(1); - assertThat(((JTypeSymbol) typeOf(c).symbol()).owner()) + assertThat(((JDTTypeSymbol) typeOf(c).symbol()).owner()) .isOfSameTypeAs(c.symbol().owner()) .isOfUnknownType(); - assertThat(((JTypeSymbol) typeOf(c).symbol())) - .isOfType(((JType) typeOf(c))); + assertThat(((JDTTypeSymbol) typeOf(c).symbol())) + .isOfType(((JDTType) typeOf(c))); - assertThat(((JVariableSymbol) symbolOf(field))) - .isOfType(((JType) symbolOf(field).type())); + assertThat(((JDTVariableSymbol) symbolOf(field))) + .isOfType(((JDTType) symbolOf(field).type())); - assertThat(((JMethodSymbol) symbolOf(method))).isOfUnknownType(); + assertThat(((JDTMethodSymbol) symbolOf(method))).isOfUnknownType(); } @Test @@ -401,8 +404,8 @@ void var_type_as_local_variable() { assertThat(variable.symbol()) .isOfType("java.util.ArrayList"); - assertThat(((JVariableSymbol) symbolOf(variable))) - .isOfType(((JType) symbolOf(variable).type())); + assertThat(((JDTVariableSymbol) symbolOf(variable))) + .isOfType(((JDTType) symbolOf(variable).type())); } @Test @@ -439,10 +442,10 @@ void test_with_type_null() { ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); astParser.setSource("class C { void m(String p) { } }".toCharArray()); CompilationUnit cu = (CompilationUnit) astParser.createAST(null); - JSema jSema = new JSema(cu.getAST()); + JDTSema jSema = new JDTSema(cu.getAST()); IVariableBinding mock = mock(IVariableBinding.class); when(mock.getKind()).thenReturn(IBinding.VARIABLE); - JVariableSymbol jVariableSymbol = new JVariableSymbol(jSema, mock); + JDTVariableSymbol jVariableSymbol = new JDTVariableSymbol(jSema, mock); assertThat(jVariableSymbol.metadata()).isNotNull(); } @@ -464,30 +467,30 @@ void parse() { @Test void isPackageSymbol() { - JTypeSymbol c1Symbol = ((JTypeSymbol) typeOf(c1).symbol()); + JDTTypeSymbol c1Symbol = ((JDTTypeSymbol) typeOf(c1).symbol()); assertThat(c1Symbol.isPackageSymbol()).isFalse(); assertThat(c1Symbol.owner().isPackageSymbol()).isTrue(); } @Test void isTypeSymbol() { - assertThat(((JTypeSymbol) typeOf(c1).symbol()).isTypeSymbol()).isTrue(); - assertThat(((JMethodSymbol) symbolOf(foo)).isTypeSymbol()).isFalse(); - assertThat(((JVariableSymbol) symbolOf(f)).isTypeSymbol()).isFalse(); + assertThat(((JDTTypeSymbol) typeOf(c1).symbol()).isTypeSymbol()).isTrue(); + assertThat(((JDTMethodSymbol) symbolOf(foo)).isTypeSymbol()).isFalse(); + assertThat(((JDTVariableSymbol) symbolOf(f)).isTypeSymbol()).isFalse(); } @Test void isMethodSymbol() { - assertThat(((JTypeSymbol) typeOf(c1).symbol()).isMethodSymbol()).isFalse(); - assertThat(((JMethodSymbol) symbolOf(foo)).isMethodSymbol()).isTrue(); - assertThat(((JVariableSymbol) symbolOf(f)).isMethodSymbol()).isFalse(); + assertThat(((JDTTypeSymbol) typeOf(c1).symbol()).isMethodSymbol()).isFalse(); + assertThat(((JDTMethodSymbol) symbolOf(foo)).isMethodSymbol()).isTrue(); + assertThat(((JDTVariableSymbol) symbolOf(f)).isMethodSymbol()).isFalse(); } @Test void isVariableSymbol() { - assertThat(((JTypeSymbol) typeOf(c1).symbol()).isVariableSymbol()).isFalse(); - assertThat(((JMethodSymbol) symbolOf(foo)).isVariableSymbol()).isFalse(); - assertThat(((JVariableSymbol) symbolOf(f)).isVariableSymbol()).isTrue(); + assertThat(((JDTTypeSymbol) typeOf(c1).symbol()).isVariableSymbol()).isFalse(); + assertThat(((JDTMethodSymbol) symbolOf(foo)).isVariableSymbol()).isFalse(); + assertThat(((JDTVariableSymbol) symbolOf(f)).isVariableSymbol()).isTrue(); } } @@ -589,10 +592,10 @@ void isVolatile() { + " int b;" + "}"); - JVariableSymbol a = variable(cu, 0); + JDTVariableSymbol a = variable(cu, 0); assertThat(a.isVolatile()).isTrue(); - JVariableSymbol b = variable(cu, 1); + JDTVariableSymbol b = variable(cu, 1); assertThat(b.isVolatile()).isFalse(); } @@ -602,8 +605,8 @@ void isInterface() { "interface I1 { } " + "class C1 { }"); - assertThat(((JTypeSymbol) typeOf(((ClassTreeImpl) cu.types().get(0))).symbol()).isInterface()).isTrue(); - assertThat(((JTypeSymbol) typeOf(((ClassTreeImpl) cu.types().get(1))).symbol()).isInterface()).isFalse(); + assertThat(((JDTTypeSymbol) typeOf(((ClassTreeImpl) cu.types().get(0))).symbol()).isInterface()).isTrue(); + assertThat(((JDTTypeSymbol) typeOf(((ClassTreeImpl) cu.types().get(1))).symbol()).isInterface()).isFalse(); } @Test @@ -611,39 +614,39 @@ void isEnum() { JavaTree.CompilationUnitTreeImpl cu = test("class C1 { int a; void foo() { }} enum E1 { ACONST; }"); ClassTreeImpl c1 = (ClassTreeImpl) cu.types().get(0); - JTypeSymbol c1Symbol = ((JTypeSymbol) typeOf(c1).symbol()); + JDTTypeSymbol c1Symbol = ((JDTTypeSymbol) typeOf(c1).symbol()); assertThat(c1Symbol.isEnum()).isFalse(); - JVariableSymbol a = ((JVariableSymbol) symbolOf(((VariableTreeImpl) c1.members().get(0)))); + JDTVariableSymbol a = ((JDTVariableSymbol) symbolOf(((VariableTreeImpl) c1.members().get(0)))); assertThat(a.isEnum()).isFalse(); - JMethodSymbol foo = ((JMethodSymbol) symbolOf(((MethodTreeImpl) c1.members().get(1)))); + JDTMethodSymbol foo = ((JDTMethodSymbol) symbolOf(((MethodTreeImpl) c1.members().get(1)))); assertThat(foo.isEnum()).isFalse(); ClassTreeImpl e1 = (ClassTreeImpl) cu.types().get(1); - JTypeSymbol e1Symbol = ((JTypeSymbol) typeOf(e1).symbol()); + JDTTypeSymbol e1Symbol = ((JDTTypeSymbol) typeOf(e1).symbol()); assertThat(e1Symbol.isEnum()).isTrue(); - JVariableSymbol aconst = ((JVariableSymbol) symbolOf(((EnumConstantTreeImpl) e1.members().get(0)))); + JDTVariableSymbol aconst = ((JDTVariableSymbol) symbolOf(((EnumConstantTreeImpl) e1.members().get(0)))); assertThat(aconst.isEnum()).isTrue(); } - private JMethodSymbol foo(JavaTree.CompilationUnitTreeImpl cu) { + private JDTMethodSymbol foo(JavaTree.CompilationUnitTreeImpl cu) { return method(cu, 0); } - private JMethodSymbol bar(JavaTree.CompilationUnitTreeImpl cu) { + private JDTMethodSymbol bar(JavaTree.CompilationUnitTreeImpl cu) { return method(cu, 1); } - private JMethodSymbol method(JavaTree.CompilationUnitTreeImpl cu, int memberIndex) { + private JDTMethodSymbol method(JavaTree.CompilationUnitTreeImpl cu, int memberIndex) { ClassTreeImpl c1 = (ClassTreeImpl) cu.types().get(0); - return ((JMethodSymbol) symbolOf(((MethodTreeImpl) c1.members().get(memberIndex)))); + return ((JDTMethodSymbol) symbolOf(((MethodTreeImpl) c1.members().get(memberIndex)))); } - private JVariableSymbol variable(JavaTree.CompilationUnitTreeImpl cu, int memberIndex) { + private JDTVariableSymbol variable(JavaTree.CompilationUnitTreeImpl cu, int memberIndex) { ClassTreeImpl c1 = (ClassTreeImpl) cu.types().get(0); - return ((JVariableSymbol) symbolOf(((VariableTreeImpl) c1.members().get(memberIndex)))); + return ((JDTVariableSymbol) symbolOf(((VariableTreeImpl) c1.members().get(memberIndex)))); } } @@ -756,10 +759,10 @@ int f(int i) { """); ClassTree cClassTree = (ClassTree) cu.types().get(0); - JSymbol cSymbol = (JSymbol) cClassTree.symbol(); + JDTSymbol cSymbol = (JDTSymbol) cClassTree.symbol(); MethodTree f = (MethodTree) cClassTree.members().get(0); - JSymbol fSymbol = (JSymbol) f.symbol(); + JDTSymbol fSymbol = (JDTSymbol) f.symbol(); assertNotEquals(fSymbol, cSymbol); // Symmetric case diff --git a/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JTypeSymbolTest.java similarity index 79% rename from java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JTypeSymbolTest.java index 2b797758a4b..0b7b88e2687 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JTypeSymbolTest.java @@ -14,11 +14,13 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Objects; import org.eclipse.jdt.core.dom.ITypeBinding; import org.junit.jupiter.api.Test; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; @@ -28,9 +30,9 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.sonar.java.model.JParserTestUtils.sema; +import static org.sonar.java.jdt.JdtTestUtils.sema; +import static org.sonar.java.jdt.JdtTestUtils.typeBinding; import static org.sonar.java.model.JParserTestUtils.symbolOf; -import static org.sonar.java.model.JParserTestUtils.typeBinding; import static org.sonar.java.model.JParserTestUtils.typeOf; class JTypeSymbolTest { @@ -70,9 +72,9 @@ void testSuperClassWhenSemanticCanNotResolveObjectType() { var cu = test("interface I{}"); var i = (ClassTreeImpl) cu.types().get(0); - JSema sematicThatCanNotResolveObjectType = spy(sema(cu)); + JDTSema sematicThatCanNotResolveObjectType = spy(sema(cu)); when(sematicThatCanNotResolveObjectType.resolveType("java.lang.Object")).thenReturn(null); - var typeSymbol = new JTypeSymbol(sematicThatCanNotResolveObjectType, typeBinding(i)); + var typeSymbol = new JDTTypeSymbol(sematicThatCanNotResolveObjectType, typeBinding(i)); assertThat(typeSymbol.superClass()).isSameAs(Type.UNKNOWN); } @@ -94,11 +96,11 @@ void memberSymbols() { VariableTreeImpl f = (VariableTreeImpl) c.members().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(1); ClassTreeImpl n = (ClassTreeImpl) c.members().get(2); - assertThat(((JTypeSymbol) typeOf(c).symbol()).memberSymbols()) + assertThat(((JDTTypeSymbol) typeOf(c).symbol()).memberSymbols()) .containsOnly( - ((JVariableSymbol) symbolOf(f)), - ((JMethodSymbol) symbolOf(m)), - ((JTypeSymbol) typeOf(n).symbol()) + ((JDTVariableSymbol) symbolOf(f)), + ((JDTMethodSymbol) symbolOf(m)), + ((JDTTypeSymbol) typeOf(n).symbol()) ); } @@ -109,21 +111,21 @@ void superTypesTest(){ assertThat(sema(cu).typeSymbol(javaLangObject).superTypes()).isEmpty(); ClassTreeImpl interfaceI = (ClassTreeImpl) cu.types().get(0); - JTypeSymbol interfaceITypeSymbol = ((JTypeSymbol) typeOf(interfaceI).symbol()); + JDTTypeSymbol interfaceITypeSymbol = ((JDTTypeSymbol) typeOf(interfaceI).symbol()); assertThat(interfaceITypeSymbol.superTypes()).isEmpty(); assertThat(interfaceITypeSymbol.superTypes()).isEmpty(); // repeat call to cover cache ClassTreeImpl classC = (ClassTreeImpl) cu.types().get(1); - JTypeSymbol classCTypeSymbol = ((JTypeSymbol) typeOf(classC).symbol()); + JDTTypeSymbol classCTypeSymbol = ((JDTTypeSymbol) typeOf(classC).symbol()); assertThat(classCTypeSymbol.superTypes()).containsExactly(interfaceITypeSymbol.type(), sema(cu).type(javaLangObject)); ClassTreeImpl classC2 = (ClassTreeImpl) cu.types().get(2); - JTypeSymbol classC2TypeSymbol = ((JTypeSymbol) typeOf(classC2).symbol()); + JDTTypeSymbol classC2TypeSymbol = ((JDTTypeSymbol) typeOf(classC2).symbol()); assertThat(classC2TypeSymbol.superTypes()).containsExactly(classCTypeSymbol.type() ,interfaceITypeSymbol.type(), sema(cu).type(javaLangObject)); ITypeBinding brokenTypeBinding = spy(typeBinding(classC2)); when(brokenTypeBinding.isRecovered()).thenReturn(true); - JTypeSymbol brokenTypeSymbol = new JTypeSymbol(sema(cu), brokenTypeBinding); + JDTTypeSymbol brokenTypeSymbol = new JDTTypeSymbol(sema(cu), brokenTypeBinding); assertThat(brokenTypeSymbol.superTypes()).isEmpty(); } @@ -132,37 +134,37 @@ void outermostClassTest() { JavaTree.CompilationUnitTreeImpl cu = test("class C { class N {} }"); ClassTreeImpl outerClass = (ClassTreeImpl) cu.types().get(0); ClassTreeImpl innerClass = (ClassTreeImpl) outerClass.members().get(0); - JTypeSymbol innerClassSymbol = ((JTypeSymbol) typeOf(innerClass).symbol()); - JTypeSymbol outerClassSymbol = ((JTypeSymbol) typeOf(outerClass).symbol()); + JDTTypeSymbol innerClassSymbol = ((JDTTypeSymbol) typeOf(innerClass).symbol()); + JDTTypeSymbol outerClassSymbol = ((JDTTypeSymbol) typeOf(outerClass).symbol()); assertThat(innerClassSymbol.outermostClass()).isSameAs(outerClassSymbol); assertThat(innerClassSymbol.outermostClass()).isSameAs(outerClassSymbol); } @Test void isAnnotationTest(){ - JTypeSymbol annotationSymbol = getJTypeSymbolFromClassText("@interface A { }", true); + JDTTypeSymbol annotationSymbol = getJTypeSymbolFromClassText("@interface A { }", true); assertThat(annotationSymbol.isAnnotation()).isFalse(); annotationSymbol = getJTypeSymbolFromClassText("@interface A { }", false); assertThat(annotationSymbol.isAnnotation()).isTrue(); - JTypeSymbol classSymbol = getJTypeSymbolFromClassText("class C { }", false); + JDTTypeSymbol classSymbol = getJTypeSymbolFromClassText("class C { }", false); assertThat(classSymbol.isAnnotation()).isFalse(); } @Test void isEffectivelyFinalTest(){ - JTypeSymbol classSymbol = getJTypeSymbolFromClassText("class C { }", false); + JDTTypeSymbol classSymbol = getJTypeSymbolFromClassText("class C { }", false); assertThat(classSymbol.superSymbol.isEffectivelyFinal()).isFalse(); } - private static JTypeSymbol getJTypeSymbolFromClassText(String classText, boolean isUnknown){ + private static JDTTypeSymbol getJTypeSymbolFromClassText(String classText, boolean isUnknown){ JavaTree.CompilationUnitTreeImpl cu = test(classText); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); if(isUnknown){ ITypeBinding brokenTypeBinding = spy(typeBinding(c)); when(brokenTypeBinding.isRecovered()).thenReturn(true); - return new JTypeSymbol(sema(cu), brokenTypeBinding); + return new JDTTypeSymbol(sema(cu), brokenTypeBinding); } - return new JTypeSymbol(sema(cu), typeBinding(c)); + return new JDTTypeSymbol(sema(cu), typeBinding(c)); } private static JavaTree.CompilationUnitTreeImpl test(String source) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JTypeTest.java similarity index 91% rename from java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JTypeTest.java index 751873fd1c9..d8a7f837048 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JTypeTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Objects; import java.util.stream.Stream; @@ -28,6 +28,9 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.event.Level; +import org.sonar.java.model.AbstractTypedTree; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; import org.sonar.java.model.JavaTree.CompilationUnitTreeImpl; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; @@ -96,7 +99,7 @@ void isParametrizedType() { ExpressionStatementTreeImpl s = (ExpressionStatementTreeImpl) Objects.requireNonNull(m.block()).body().get(0); AbstractTypedTree e = Objects.requireNonNull((AbstractTypedTree) s.expression()); - assertThat(((JType) typeOf(e)).isParameterized()) + assertThat(((JDTType) typeOf(e)).isParameterized()) .isEqualTo(e.symbolType().isParameterized()) .isTrue(); } @@ -109,11 +112,11 @@ void typeArguments() { ExpressionStatementTreeImpl s = (ExpressionStatementTreeImpl) Objects.requireNonNull(m.block()).body().get(0); AbstractTypedTree e = Objects.requireNonNull((AbstractTypedTree) s.expression()); - assertThat(((JType) typeOf(e)).typeArguments()) + assertThat(((JDTType) typeOf(e)).typeArguments()) .hasToString(e.symbolType().typeArguments().toString()) .hasToString("[Integer, String]"); - assertThat(((JType) typeOf(c)).typeArguments()) + assertThat(((JDTType) typeOf(c)).typeArguments()) .isEqualTo(c.symbol().type().typeArguments()) .isEmpty(); } @@ -172,7 +175,7 @@ void primitiveWrapperType_returns_null_when_semantic_cannot_resolve_wrapper_type when(brokenSema.resolveType("java.lang.Byte")).thenReturn(null); var primitiveBinding = sema.resolveType("byte"); - var primitiveType = new JType(brokenSema, primitiveBinding); + var primitiveType = new JDTType(brokenSema, primitiveBinding); assertThat(primitiveType.primitiveWrapperType()).isNull(); } @@ -185,7 +188,7 @@ void primitiveType_returns_null_when_semantic_cannot_resolve_primitive_type() { when(brokenSema.resolveType("byte")).thenReturn(null); var wrapperBinding = sema.resolveType("java.lang.Byte"); - var wrapperType = new JType(brokenSema, wrapperBinding); + var wrapperType = new JDTType(brokenSema, wrapperBinding); assertThat(wrapperType.primitiveType()).isNull(); } @@ -236,11 +239,11 @@ void name_of_parameterized_type() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); AbstractTypedTree e = Objects.requireNonNull((AbstractTypedTree) m.returnType()); - JType parameterizedType = ((JType) typeOf(e)); + JDTType parameterizedType = ((JDTType) typeOf(e)); assertThat(parameterizedType) .hasSameNameAs(e.symbolType()) - .hasSameNameAs(((JTypeSymbol) typeOf(e).symbol()).type()) + .hasSameNameAs(((JDTTypeSymbol) typeOf(e).symbol()).type()) .is("java.util.List") .hasName("List"); } @@ -251,10 +254,10 @@ void name_of_type_variable() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); AbstractTypedTree e = Objects.requireNonNull((AbstractTypedTree) m.returnType()); - JType typeVariable = ((JType) typeOf(e)); + JDTType typeVariable = ((JDTType) typeOf(e)); assertThat(typeVariable) .hasSameNameAs(e.symbolType()) - .hasSameNameAs(((JTypeSymbol) typeOf(e).symbol()).type()) + .hasSameNameAs(((JDTTypeSymbol) typeOf(e).symbol()).type()) .hasName("T") .is(e.symbolType().fullyQualifiedName()) .is("T"); @@ -309,10 +312,10 @@ void null_type() { AbstractTypedTree primitive = (AbstractTypedTree) m.parameters().get(0).type(); AbstractTypedTree array = (AbstractTypedTree) m.parameters().get(1).type(); - JType nullType = ((JType) typeOf(e)); - JType classType = ((JType) typeOf(c)); - JType primitiveType = ((JType) typeOf(primitive)); - JType arrayType = ((JType) typeOf(array)); + JDTType nullType = ((JDTType) typeOf(e)); + JDTType classType = ((JDTType) typeOf(c)); + JDTType primitiveType = ((JDTType) typeOf(primitive)); + JDTType arrayType = ((JDTType) typeOf(array)); assertThat(nullType) .hasSameNameAs(e.symbolType()) @@ -326,13 +329,13 @@ void null_type() { @Test void is_subtype_of_should_not_throw_NPE() { - JType objectType = type("java.lang.Object"); + JDTType objectType = type("java.lang.Object"); ITypeBinding brokenStringBinding = spy(Objects.requireNonNull(sema.resolveType("java.lang.String"))); // simulate the NullPointerException described in SONARJAVA-4390 when(brokenStringBinding.isSubTypeCompatible(any())) .thenThrow(new NullPointerException("test NPE")); - JType stringType = new JType(sema, brokenStringBinding); + JDTType stringType = new JDTType(sema, brokenStringBinding); // should catch the NullPointerException assertThat(stringType.isSubtypeOf(objectType)).isFalse(); @@ -349,9 +352,9 @@ void wildcard() { JavaTree.WildcardTreeImpl w1 = (JavaTree.WildcardTreeImpl) p.typeArguments().get(0); JavaTree.WildcardTreeImpl w2 = (JavaTree.WildcardTreeImpl) p.typeArguments().get(1); JavaTree.WildcardTreeImpl w3 = (JavaTree.WildcardTreeImpl) p.typeArguments().get(2); - JType wildcardType1 = ((JType) typeOf(w1)); - JType wildcardType2 = ((JType) typeOf(w2)); - JType wildcardType3 = ((JType) typeOf(w3)); + JDTType wildcardType1 = ((JDTType) typeOf(w1)); + JDTType wildcardType2 = ((JDTType) typeOf(w2)); + JDTType wildcardType3 = ((JDTType) typeOf(w3)); assertThat(wildcardType1) .isEqualTo(wildcardType2) @@ -362,12 +365,12 @@ private static JavaTree.CompilationUnitTreeImpl test(String source) { return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); } - private JType type(String name) { + private JDTType type(String name) { ITypeBinding typeBinding = Objects.requireNonNull(sema.resolveType(name)); return sema.type(typeBinding); } - private JSema sema; + private JDTSema sema; @BeforeEach void setup() { @@ -382,7 +385,7 @@ void setup() { astParser.setUnitName("File.java"); astParser.setSource("".toCharArray()); AST ast = astParser.createAST(null).getAST(); - sema = new JSema(ast); + sema = new JDTSema(ast); } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JUtilsTest.java similarity index 93% rename from java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JUtilsTest.java index ce220e00d0c..ef99c658a0a 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JUtilsTest.java @@ -14,12 +14,17 @@ * 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.model; +package org.sonar.java.jdt; import java.util.List; import java.util.Set; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.sonar.java.model.AbstractTypedTree; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JUtils; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.Symbols; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; @@ -37,14 +42,14 @@ import org.sonar.plugins.java.api.tree.TypeParameterTree; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.java.model.JParserTestUtils.methodBinding; -import static org.sonar.java.model.JParserTestUtils.sema; +import static org.sonar.java.jdt.JdtTestUtils.methodBinding; +import static org.sonar.java.jdt.JdtTestUtils.sema; import static org.sonar.java.model.JParserTestUtils.symbolOf; import static org.sonar.java.model.JParserTestUtils.typeOf; class JUtilsTest { - static final JSema SEMA = sema(test("")); + static final JDTSema SEMA = sema(test("")); private static final Type OBJECT_TYPE = SEMA.type(SEMA.resolveType("java.lang.Object")); @Nested @@ -144,7 +149,7 @@ class IsNullType { void nullType() { ReturnStatementTreeImpl s = (ReturnStatementTreeImpl) m1.block().body().get(0); AbstractTypedTree e = (AbstractTypedTree) s.expression(); - assertThat(((JType) typeOf(e)).isNullType()) + assertThat(((JDTType) typeOf(e)).isNullType()) .isEqualTo(e.symbolType().isNullType()) .isTrue(); } @@ -182,14 +187,14 @@ class C { @Test void intersection_type() { TypeCastExpressionTreeImpl e = (TypeCastExpressionTreeImpl) f.initializer(); - assertThat(JUtils.isIntersectionType(((JType) typeOf(e)))) + assertThat(JUtils.isIntersectionType(((JDTType) typeOf(e)))) .isEqualTo(JUtils.isIntersectionType(e.symbolType())) .isTrue(); } @Test void non_intersection_type() { - assertThat(JUtils.isIntersectionType(((JType) symbolOf(f).type()))) + assertThat(JUtils.isIntersectionType(((JDTType) symbolOf(f).type()))) .isEqualTo(JUtils.isIntersectionType(f.symbol().type())) .isFalse(); } @@ -197,7 +202,7 @@ void non_intersection_type() { @Test void unresolved_type_is_not_an_intersection_type() { VariableTreeImpl u = nthField(c, 1); - assertThat(JUtils.isIntersectionType(((JType) symbolOf(u).type()))) + assertThat(JUtils.isIntersectionType(((JDTType) symbolOf(u).type()))) .isEqualTo(JUtils.isIntersectionType(u.symbol().type())) .isFalse(); } @@ -211,7 +216,7 @@ class IsTypeVar { @Test void type_var() { VariableTreeImpl t = firstField(c); - assertThat(((JType) symbolOf(t).type()).isTypeVar()) + assertThat(((JDTType) symbolOf(t).type()).isTypeVar()) .isEqualTo(t.symbol().type().isTypeVar()) .isTrue(); } @@ -224,7 +229,7 @@ void simple_type_is_not_a_type_var() { @Test void unresolved_type_is_not_a_type_var() { VariableTreeImpl u = nthField(c, 1); - assertThat(((JType) symbolOf(u).type()).isTypeVar()) + assertThat(((JDTType) symbolOf(u).type()).isTypeVar()) .isEqualTo(u.symbol().type().isTypeVar()) .isFalse(); } @@ -388,22 +393,22 @@ void constantValue() { JavaTree.CompilationUnitTreeImpl specificCu = test("interface I { short SHORT = 42; char CHAR = 42; byte BYTE = 42; boolean BOOLEAN = false; }"); ClassTreeImpl specificC = firstClass(specificCu); - Symbol.VariableSymbol shortConstant = ((JVariableSymbol) symbolOf(firstField(specificC))); + Symbol.VariableSymbol shortConstant = ((JDTVariableSymbol) symbolOf(firstField(specificC))); assertThat(shortConstant.constantValue().orElseThrow(AssertionError::new)) .isInstanceOf(Integer.class) .isEqualTo(42); - Symbol.VariableSymbol charConstant = ((JVariableSymbol) symbolOf(nthField(specificC, 1))); + Symbol.VariableSymbol charConstant = ((JDTVariableSymbol) symbolOf(nthField(specificC, 1))); assertThat(charConstant.constantValue().orElseThrow(AssertionError::new)) .isInstanceOf(Integer.class) .isEqualTo(42); - Symbol.VariableSymbol byteConstant = ((JVariableSymbol) symbolOf(nthField(specificC, 2))); + Symbol.VariableSymbol byteConstant = ((JDTVariableSymbol) symbolOf(nthField(specificC, 2))); assertThat(byteConstant.constantValue().orElseThrow(AssertionError::new)) .isInstanceOf(Integer.class) .isEqualTo(42); - Symbol.VariableSymbol booleanConstant = ((JVariableSymbol) symbolOf(nthField(specificC, 3))); + Symbol.VariableSymbol booleanConstant = ((JDTVariableSymbol) symbolOf(nthField(specificC, 3))); assertThat(booleanConstant.constantValue().orElseThrow(AssertionError::new)) .isInstanceOf(Boolean.class) .isEqualTo(Boolean.FALSE); @@ -609,7 +614,7 @@ void unknown_method_is_not_default() { } @Nested - class DefaultValue { + class AnnotationElementDefaultValue { private final JavaTree.CompilationUnitTreeImpl cu = test(""" @interface A { int foo() default 42; @@ -619,7 +624,7 @@ class DefaultValue { private final ClassTreeImpl a = firstClass(cu); @Test - void defaultValue() { + void annotationElementDefaultValue() { MethodTreeImpl defaultValueMethod = firstMethod(a); assertThat(JUtils.defaultValue(defaultValueMethod.symbol())).isEqualTo(42); } @@ -709,11 +714,11 @@ void isParametrizedMethod() { ExpressionStatementTreeImpl s = (ExpressionStatementTreeImpl) m.block().body().get(0); MethodInvocationTreeImpl methodInvocation = (MethodInvocationTreeImpl) s.expression(); - assertThat(((JMethodSymbol) symbolOf(m)).isParametrizedMethod()) + assertThat(((JDTMethodSymbol) symbolOf(m)).isParametrizedMethod()) .isEqualTo(m.symbol().isParametrizedMethod()) .isEqualTo(methodBinding(m).isGenericMethod()) .isTrue(); - assertThat(((JMethodSymbol) symbolOf(methodInvocation)).isParametrizedMethod()) + assertThat(((JDTMethodSymbol) symbolOf(methodInvocation)).isParametrizedMethod()) .isEqualTo(methodInvocation.methodSymbol().isParametrizedMethod()) .isEqualTo(methodBinding(methodInvocation).isParameterizedMethod()) .isTrue(); @@ -782,6 +787,29 @@ void object_type_has_no_direct_supertypes() { assertThat(JUtils.directSuperTypes(OBJECT_TYPE)).isEmpty(); } + @Test + void interface_without_parents_has_no_direct_supertypes() { + ClassTreeImpl c = firstClass(test("interface C {}")); + + assertThat(JUtils.directSuperTypes(c.symbol().type())).isEmpty(); + } + + @Test + void interface_direct_supertypes_include_only_extended_interfaces() { + ClassTreeImpl c = firstClass(test("interface C extends java.io.Serializable, java.lang.Cloneable {}")); + + assertThat(JUtils.directSuperTypes(c.symbol().type())) + .extracting(Type::fullyQualifiedName) + .containsExactlyInAnyOrder("java.io.Serializable", "java.lang.Cloneable"); + } + + @Test + void array_type_has_no_direct_supertypes() { + ClassTreeImpl c = firstClass(test("class C { String[] values; }")); + + assertThat(JUtils.directSuperTypes(firstField(c).symbol().type())).isEmpty(); + } + @Test void direct_supertypes_might_be_equal_to_all_supertypes() { ClassTreeImpl c = firstClass(cu); @@ -1001,7 +1029,7 @@ void parameterAnnotations() { @Test void test_has_unknown_type_in_hierarchy_with_unexpected_null_owner() { - JInitializerBlockSymbol method = new JInitializerBlockSymbol(null, true); + JDTInitializerBlockSymbol method = new JDTInitializerBlockSymbol(null, true); assertThat(JUtils.hasUnknownTypePreventingOverrideResolution(method)).isTrue(); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JVariableSymbolTest.java similarity index 87% rename from java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JVariableSymbolTest.java index a7c347d0f27..4edd8261b08 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JVariableSymbolTest.java @@ -14,9 +14,11 @@ * 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.model; +package org.sonar.java.jdt; import org.junit.jupiter.api.Test; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; @@ -36,10 +38,10 @@ void isLocalVariable() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl method = (MethodTreeImpl) c.members().get(0); VariableTreeImpl localVariable = (VariableTreeImpl) method.block().body().get(0); - JVariableSymbol variableSymbol = ((JVariableSymbol) symbolOf(localVariable)); + JDTVariableSymbol variableSymbol = ((JDTVariableSymbol) symbolOf(localVariable)); assertThat(variableSymbol.isLocalVariable()).isTrue(); VariableTreeImpl classVariable = (VariableTreeImpl) c.members().get(1); - variableSymbol = ((JVariableSymbol) symbolOf(classVariable)); + variableSymbol = ((JDTVariableSymbol) symbolOf(classVariable)); assertThat(variableSymbol.isLocalVariable()).isFalse(); } @@ -62,11 +64,11 @@ void isParameter() { MethodTreeImpl method = (MethodTreeImpl) c.members().get(0); VariableTreeImpl localVariable = (VariableTreeImpl) method.block().body().get(0); - JVariableSymbol variableSymbol = ((JVariableSymbol) symbolOf(localVariable)); + JDTVariableSymbol variableSymbol = ((JDTVariableSymbol) symbolOf(localVariable)); assertThat(variableSymbol.isParameter()).isFalse(); VariableTreeImpl parameter = (VariableTreeImpl) method.parameters().get(0); - variableSymbol = ((JVariableSymbol) symbolOf(parameter)); + variableSymbol = ((JDTVariableSymbol) symbolOf(parameter)); assertThat(variableSymbol.isParameter()).isTrue(); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JWarningTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JWarningTest.java similarity index 79% rename from java-frontend/src/test/java/org/sonar/java/model/JWarningTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JWarningTest.java index ab635cc19ac..27e08e0bffa 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JWarningTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JWarningTest.java @@ -14,7 +14,7 @@ * 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.model; +package org.sonar.java.jdt; import java.util.Collections; import org.eclipse.jdt.core.compiler.IProblem; @@ -22,6 +22,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.sonar.java.model.InternalSyntaxToken; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.LineColumnConverter; import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.java.model.expression.IdentifierTreeImpl; import org.sonar.plugins.java.api.diagnostics.ProblemKind; @@ -39,10 +42,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.sonar.java.model.JWarning.Mapper.isInsideTree; -import static org.sonar.java.model.JWarning.Mapper.isMorePreciseTree; -import static org.sonar.java.model.JWarning.Mapper.matchesTreeExactly; -import static org.sonar.java.model.JWarning.Mapper.setSyntaxTree; +import static org.sonar.java.jdt.JDTWarning.Mapper.isInsideTree; +import static org.sonar.java.jdt.JDTWarning.Mapper.isMorePreciseTree; +import static org.sonar.java.jdt.JDTWarning.Mapper.matchesTreeExactly; +import static org.sonar.java.jdt.JDTWarning.Mapper.setSyntaxTree; class JWarningTest { @@ -54,7 +57,7 @@ void ecj_conversion_preserves_source_range(int sourceEnd, int endLine, int endCo when(problem.getMessage()).thenReturn("Unused import"); when(problem.getSourceStart()).thenReturn(2); when(problem.getSourceEnd()).thenReturn(sourceEnd); - JWarning warning = JWarning.Mapper.convert(problem, new LineColumnConverter("x\nimport\njava.util.List;")); + JDTWarning warning = JDTWarning.Mapper.convert(problem, new LineColumnConverter("x\nimport\njava.util.List;")); assertThat(warning.type()).isEqualTo(ProblemKind.UNUSED_IMPORT); assertThat(warning.message()).isEqualTo("Unused import"); assertThat(warning.start().line()).isEqualTo(2); @@ -69,7 +72,7 @@ void ignores_diagnostics_that_cannot_be_associated_with_supported_warning_trees( for (int id : new int[]{IProblem.ParsingError, IProblem.UndefinedType}) { IProblem problem = mock(IProblem.class); when(problem.getID()).thenReturn(id); - assertThat(JWarning.Mapper.convert(problem, converter)).isNull(); + assertThat(JDTWarning.Mapper.convert(problem, converter)).isNull(); } } @@ -81,7 +84,7 @@ void getters() { int startColumn = 1; int endLine = 2; int endColumn = 3; - JWarning warning = new JWarning(message, type, startLine, startColumn, endLine, endColumn); + JDTWarning warning = new JDTWarning(message, type, startLine, startColumn, endLine, endColumn); assertThat(warning.message()).isEqualTo(message); assertThat(warning.type()).isEqualTo(type); @@ -90,43 +93,43 @@ void getters() { @Test void test_equals() { - JWarning j1 = new JWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); + JDTWarning j1 = new JDTWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); assertFalse(j1.equals(null)); assertTrue(j1.equals(j1)); - JWarning j2 = new JWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); + JDTWarning j2 = new JDTWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); assertTrue(j1.equals(j2)); - // syntaxTree is ignored in JWarning.equals() + // syntaxTree is ignored in JDTWarning.equals() Tree beforeDifferentLine = importTree(4, 0, 5, 19); setSyntaxTree(j2, beforeDifferentLine); assertTrue(j1.equals(j2)); - j2 = new JWarning("b", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); + j2 = new JDTWarning("b", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); assertFalse(j1.equals(j2)); - j2 = new JWarning("a", ProblemKind.UNUSED_IMPORT, 42, 1, 2, 3); + j2 = new JDTWarning("a", ProblemKind.UNUSED_IMPORT, 42, 1, 2, 3); assertFalse(j1.equals(j2)); - j2 = new JWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 42, 3); + j2 = new JDTWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 42, 3); assertFalse(j1.equals(j2)); } @Test void test_hashcode() { - JWarning j1 = new JWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); - JWarning j2 = new JWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); + JDTWarning j1 = new JDTWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); + JDTWarning j2 = new JDTWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3); assertEquals(j1.hashCode(), j2.hashCode()); - // syntaxTree is ignored in JWarning.hashCode() + // syntaxTree is ignored in JDTWarning.hashCode() Tree beforeDifferentLine = importTree(4, 0, 5, 19); setSyntaxTree(j2, beforeDifferentLine); assertEquals(j1.hashCode(), j2.hashCode()); assertNotEquals( - new JWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3).hashCode(), - new JWarning("b", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3).hashCode()); + new JDTWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3).hashCode(), + new JDTWarning("b", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3).hashCode()); assertNotEquals( - new JWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3).hashCode(), - new JWarning("a", ProblemKind.REDUNDANT_CAST, 0, 1, 2, 3).hashCode()); + new JDTWarning("a", ProblemKind.UNUSED_IMPORT, 0, 1, 2, 3).hashCode(), + new JDTWarning("a", ProblemKind.REDUNDANT_CAST, 0, 1, 2, 3).hashCode()); } @Nested @@ -137,7 +140,7 @@ class MapperTest { @Test void inside_multiline_warning() { - JWarning warning = new JWarning("message", ProblemKind.UNUSED_IMPORT, 5, 20, 7, 50); + JDTWarning warning = new JDTWarning("message", ProblemKind.UNUSED_IMPORT, 5, 20, 7, 50); Tree beforeDifferentLine = importTree(4, 0, 5, 19); Tree beforeSameStartLine = importTree(5, 15, 6, 20); @@ -158,7 +161,7 @@ void inside_multiline_warning() { @Test void inside_single_line_warning() { - JWarning warning = new JWarning("message", ProblemKind.UNUSED_IMPORT, 5, 20, 5, 50); + JDTWarning warning = new JDTWarning("message", ProblemKind.UNUSED_IMPORT, 5, 20, 5, 50); Tree beforeCompletely = importTree(3, 0, 3, 10); Tree beforeOverlapping = importTree(4, 0, 5, 30); @@ -179,7 +182,7 @@ void inside_single_line_warning() { @Test void inside_wrong_kind() { - JWarning warning = new JWarning("message", ProblemKind.UNUSED_IMPORT, 5, 20, 7, 50); + JDTWarning warning = new JDTWarning("message", ProblemKind.UNUSED_IMPORT, 5, 20, 7, 50); VariableTree variableTree = variableTree(4, 55, 8, 15); ImportTree importTree = importTree(4, 55, 8, 15); @@ -202,7 +205,7 @@ void test_isMorePreciseTree() { @Test void test_setSyntaxTree() { - JWarning warningOnType = new JWarning("message", ProblemKind.UNUSED_IMPORT, 1, 10, 1, 11); + JDTWarning warningOnType = new JDTWarning("message", ProblemKind.UNUSED_IMPORT, 1, 10, 1, 11); assertThatNullPointerException().isThrownBy(warningOnType::syntaxTree); setSyntaxTree(warningOnType, variable); @@ -218,7 +221,7 @@ void test_setSyntaxTree() { @Test void test_matchesTreeExactly() { - JWarning warningOnType = new JWarning("message", ProblemKind.UNUSED_IMPORT, 1, 10, 1, 11); + JDTWarning warningOnType = new JDTWarning("message", ProblemKind.UNUSED_IMPORT, 1, 10, 1, 11); setSyntaxTree(warningOnType, variable); assertThat(matchesTreeExactly(warningOnType)).isFalse(); @@ -226,7 +229,7 @@ void test_matchesTreeExactly() { setSyntaxTree(warningOnType, type); assertThat(matchesTreeExactly(warningOnType)).isTrue(); - JWarning warningOnTypeAgain = new JWarning("message", ProblemKind.UNUSED_IMPORT, 1, 10, 1, 11); + JDTWarning warningOnTypeAgain = new JDTWarning("message", ProblemKind.UNUSED_IMPORT, 1, 10, 1, 11); setSyntaxTree(warningOnTypeAgain, name); assertThat(matchesTreeExactly(warningOnTypeAgain)).isFalse(); } diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JdtClassesLayoutTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JdtClassesLayoutTest.java new file mode 100644 index 00000000000..77bbd90167e --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JdtClassesLayoutTest.java @@ -0,0 +1,50 @@ +/* + * 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.jdt; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.java.testing.ClassLayoutTestUtils.countFields; + +class JdtClassesLayoutTest { + + @Test + void type() { + assertThat(countFields(JDTType.class)).isEqualTo(7); + } + + @Test + void symbol_type() { + assertThat(countFields(JDTTypeSymbol.class)).isEqualTo(11); + } + + @Test + void symbol_method() { + assertThat(countFields(JDTMethodSymbol.class)).isEqualTo(11); + } + + @Test + void symbol_variable() { + assertThat(countFields(JDTVariableSymbol.class)).isEqualTo(7); + } + + @Test + void annotation() { + assertThat(countFields(JDTSymbolMetadata.JAnnotationInstance.class)).isEqualTo(3); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JdtTestUtils.java b/java-frontend/src/test/java/org/sonar/java/jdt/JdtTestUtils.java new file mode 100644 index 00000000000..9e8b387c606 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JdtTestUtils.java @@ -0,0 +1,48 @@ +/* + * 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.jdt; + +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.sonar.java.model.JavaTree; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.CompilationUnitTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.MethodTree; + +final class JdtTestUtils { + + private JdtTestUtils() { + } + + static JDTSema sema(CompilationUnitTree tree) { + return (JDTSema) ((JavaTree.CompilationUnitTreeImpl) tree).sema; + } + + static ITypeBinding typeBinding(ClassTree tree) { + return ((JDTType) tree.symbol().type()).typeBinding; + } + + static IMethodBinding methodBinding(MethodTree tree) { + return tree.symbol() instanceof JDTMethodSymbol symbol ? symbol.methodBinding() : null; + } + + static IMethodBinding methodBinding(MethodInvocationTree tree) { + return tree.methodSymbol() instanceof JDTMethodSymbol symbol ? symbol.methodBinding() : null; + } + +} diff --git a/java-frontend/src/test/java/org/sonar/java/ProgressMonitorTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/ProgressMonitorTest.java similarity index 99% rename from java-frontend/src/test/java/org/sonar/java/ProgressMonitorTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/ProgressMonitorTest.java index 3d5b0f76178..f1dae961d14 100644 --- a/java-frontend/src/test/java/org/sonar/java/ProgressMonitorTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/ProgressMonitorTest.java @@ -14,7 +14,7 @@ * 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; +package org.sonar.java.jdt; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import org.slf4j.event.Level; import org.sonar.api.testfixtures.log.LogTesterJUnit5; +import org.sonar.java.AnalysisProgress; import static org.assertj.core.api.Assertions.assertThat; diff --git a/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/SymbolsTest.java similarity index 95% rename from java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/SymbolsTest.java index dea945d72ca..299538d4631 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/SymbolsTest.java @@ -14,11 +14,14 @@ * 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.model; +package org.sonar.java.jdt; import java.io.File; import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.junit.jupiter.api.Test; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; +import org.sonar.java.model.Symbols; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; @@ -36,7 +39,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.sonar.java.model.JParserTestUtils.sema; +import static org.sonar.java.jdt.JdtTestUtils.sema; class SymbolsTest { @@ -105,7 +108,7 @@ void metadata_with_unknown_symbol_and_unsupported_declaration() { Tree unsupportedDeclaration = mock(Tree.class); Symbol symbol = spy(Symbol.UNKNOWN_SYMBOL); when(symbol.declaration()).thenReturn(unsupportedDeclaration); - SymbolMetadata metadata = new JSymbolMetadata(JUtilsTest.SEMA, symbol, new IAnnotationBinding[0]); + SymbolMetadata metadata = new JDTSymbolMetadata(JUtilsTest.SEMA, symbol, new IAnnotationBinding[0]); assertThat(metadata.findAnnotationTree(mock(AnnotationInstance.class))).isNull(); assertThat(metadata.nullabilityData().type()).isEqualTo(NullabilityType.UNKNOWN); assertThat(metadata.nullabilityData(NullabilityTarget.METHOD).type()).isEqualTo(NullabilityType.UNKNOWN); @@ -204,7 +207,7 @@ private static void assertCommonProperties(Symbol unknownSymbol) { @Test void testIsAnnotation() { - JSema sema = sema(((JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(""))); + JDTSema sema = sema(((JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(""))); Type objectType = sema.type(sema.resolveType("java.lang.Object")); JavaTree.CompilationUnitTreeImpl cu = (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse("@interface Anno { Unknown u; }"); ClassTreeImpl anno = (ClassTreeImpl) cu.types().get(0); diff --git a/java-frontend/src/test/java/org/sonar/java/model/AstSemanticValuesTest.java b/java-frontend/src/test/java/org/sonar/java/model/AstSemanticValuesTest.java index 7cd43b2415c..cf90434807f 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/AstSemanticValuesTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/AstSemanticValuesTest.java @@ -149,7 +149,7 @@ void ast_bytecode_does_not_depend_on_ecj_or_its_semantic_context() throws IOExce try (var bytecode = tree.getResourceAsStream("/" + tree.getName().replace('.', '/') + ".class")) { assertThat(new String(bytecode.readAllBytes(), ISO_8859_1)) .as(tree.getName()) - .doesNotContain("org/eclipse/jdt/", "org.eclipse.jdt.", "org/sonar/java/model/JSema"); + .doesNotContain("org/eclipse/jdt/", "org.eclipse.jdt.", "org/sonar/java/jdt/"); } } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/ClassesLayoutTest.java b/java-frontend/src/test/java/org/sonar/java/model/ClassesLayoutTest.java index 95d1f03a49e..b0707edc8b5 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/ClassesLayoutTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/ClassesLayoutTest.java @@ -16,6 +16,7 @@ */ package org.sonar.java.model; +import java.util.HashSet; import org.junit.jupiter.api.Test; import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.java.model.expression.IdentifierTreeImpl; @@ -23,11 +24,8 @@ import org.sonar.java.model.expression.MemberSelectExpressionTreeImpl; import org.sonar.java.model.expression.MethodInvocationTreeImpl; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.HashSet; - import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.java.testing.ClassLayoutTestUtils.countFields; /** * This test warns you if you are changing classes which have a big impact @@ -68,45 +66,6 @@ void method_invocation() { assertThat(countFields(MethodInvocationTreeImpl.class)).isEqualTo(8); } - @Test - void type() { - assertThat(countFields(JType.class)).isEqualTo(7); - } - - @Test - void symbol_type() { - assertThat(countFields(JTypeSymbol.class)).isEqualTo(11); - } - - @Test - void symbol_method() { - assertThat(countFields(JMethodSymbol.class)).isEqualTo(11); - } - - @Test - void symbol_variable() { - assertThat(countFields(JVariableSymbol.class)).isEqualTo(7); - } - - @Test - void annotation() { - assertThat(countFields(JSymbolMetadata.JAnnotationInstance.class)).isEqualTo(3); - } - - /** Count number of fields in instances, including inherited fields. */ - private static int countFields(Class clazz) { - int count = 0; - while (clazz != Object.class) { - for (Field f : clazz.getDeclaredFields()) { - if (!Modifier.isStatic(f.getModifiers())) { - count++; - } - } - clazz = clazz.getSuperclass(); - } - return count; - } - static class C { private int x; public long y; diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java index 663cd164410..98ba3fa0512 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java @@ -26,10 +26,9 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.JavaVersion; +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.ClassTree; @@ -51,6 +50,10 @@ private JParserTestUtils() { // Utility class } + public static Sema sema(CompilationUnitTree tree) { + return ((JavaTree.CompilationUnitTreeImpl) tree).sema; + } + public static Type typeOf(Tree tree) { return switch (tree) { case TypeTree type -> type.symbolType(); @@ -73,22 +76,6 @@ public static Symbol symbolOf(Tree tree) { }; } - public static JSema sema(CompilationUnitTree tree) { - return (JSema) ((JavaTree.CompilationUnitTreeImpl) tree).sema; - } - - public static ITypeBinding typeBinding(ClassTree tree) { - return ((JType) tree.symbol().type()).typeBinding; - } - - public static IMethodBinding methodBinding(MethodTree tree) { - return tree.symbol() instanceof JMethodSymbol symbol ? symbol.methodBinding() : null; - } - - public static IMethodBinding methodBinding(MethodInvocationTree tree) { - return tree.methodSymbol() instanceof JMethodSymbol symbol ? symbol.methodBinding() : null; - } - public static final List DEFAULT_CLASSPATH = Arrays.asList(new File("target/test-classes"), new File("target/classes")); public static final Path CHECKS_TEST_DIR = Paths.get("..", "java-checks-test-sources", "default"); @@ -132,7 +119,7 @@ public static CompilationUnitTree parse(String unitName, String source, List classpath, JavaVersion version) { - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), unitName, source); + return JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).parse(unitName, source); } public static List checksTestClassPath() throws IOException { diff --git a/java-frontend/src/test/java/org/sonar/java/testing/ClassLayoutTestUtils.java b/java-frontend/src/test/java/org/sonar/java/testing/ClassLayoutTestUtils.java new file mode 100644 index 00000000000..6a0a96cbe21 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/testing/ClassLayoutTestUtils.java @@ -0,0 +1,40 @@ +/* + * 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.testing; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public final class ClassLayoutTestUtils { + + private ClassLayoutTestUtils() { + } + + /** Count number of fields in instances, including inherited fields. */ + public static int countFields(Class clazz) { + int count = 0; + while (clazz != Object.class) { + for (Field f : clazz.getDeclaredFields()) { + if (!Modifier.isStatic(f.getModifiers())) { + count++; + } + } + clazz = clazz.getSuperclass(); + } + return count; + } +} diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/SanityTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/SanityTest.java index d622549b27e..72e9dac8545 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/SanityTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/SanityTest.java @@ -18,6 +18,7 @@ import com.sonar.sslr.api.RecognitionException; import com.sonarsource.scanner.engine.sensor.test.fixtures.SensorContextTester; +import com.sonarsource.scanner.engine.sensor.test.fixtures.TestFileSystem; import com.sonarsource.scanner.engine.sensor.test.fixtures.TestInputFileBuilder; import com.sonarsource.scanner.engine.sensor.test.fixtures.TestSonarRuntime; import java.io.File; @@ -55,7 +56,6 @@ import org.sonar.java.testing.VisitorsBridgeForTests; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.JavaVersion; -import com.sonarsource.scanner.engine.sensor.test.fixtures.TestFileSystem; import org.sonar.scanner.plugin.api.impl.fs.DefaultInputFile; import static java.nio.charset.StandardCharsets.UTF_8;