From 28d229c10c9e8b79cbd8472f7b0b1c3bd822c69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Thu, 5 Nov 2026 15:44:08 +0100 Subject: [PATCH 1/8] Decouple shared semantic utilities from JDT while preserving API names Use semantic interfaces for shared operations and expose annotation element defaults through MethodSymbol. Extract neutral nullability data while retaining JUtils and JSymbolMetadataNullabilityHelper. --- .../org/sonar/java/model/JMethodSymbol.java | 6 + .../sonar/java/model/JNullabilityData.java | 123 ++++++++++++++++ .../org/sonar/java/model/JSymbolMetadata.java | 135 +++--------------- .../JSymbolMetadataNullabilityHelper.java | 6 +- .../org/sonar/java/model/JTypeSymbol.java | 14 +- .../java/org/sonar/java/model/JUtils.java | 77 ++++------ .../java/org/sonar/java/model/Symbols.java | 5 +- .../org/sonar/java/utils/SpringUtils.java | 1 - .../plugins/java/api/semantic/Symbol.java | 12 +- .../sonar/java/model/JMethodSymbolTest.java | 42 ++++++ .../sonar/java/model/JSymbolMetadataTest.java | 20 +-- .../java/org/sonar/java/model/JUtilsTest.java | 4 +- 12 files changed, 264 insertions(+), 181 deletions(-) create mode 100644 java-frontend/src/main/java/org/sonar/java/model/JNullabilityData.java diff --git a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java index 3f15b5f9a6b..a60e528f01e 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java @@ -183,6 +183,12 @@ public boolean isParametrizedMethod() { return !isUnknown() && (methodBinding().isParameterizedMethod() || methodBinding().isGenericMethod()); } + @Nullable + @Override + public Object annotationElementDefaultValue() { + return isUnknown() ? null : JSymbolMetadata.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/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/JSymbolMetadata.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java index 2b9c5b6b862..e86f60bb32f 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java @@ -21,8 +21,6 @@ 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; @@ -39,11 +37,13 @@ final class JSymbolMetadata implements SymbolMetadata { - private static final NullabilityData[] NO_ANNOTATION_NULLABILITY = - forEachLevel(level -> new JNullabilityData(NullabilityType.NO_ANNOTATION, level, null, null, false)); + public static NullabilityData noNullabilityAnnotationAt(NullabilityLevel level) { + return JNullabilityData.noNullabilityAnnotationAt(level); + } - private static final NullabilityData[] UNKNOWN_NULLABILITY = - forEachLevel(level -> new JNullabilityData(NullabilityType.UNKNOWN, level, null, null, false)); + public static NullabilityData unknownNullabilityAt(NullabilityLevel level) { + return JNullabilityData.unknownNullabilityAt(level); + } private final JSema sema; private final Symbol symbol; @@ -118,18 +118,6 @@ private static JSymbolMetadata ofTypeArg(JSema sema, Symbol owner, ITypeBinding 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()]; - } - @Override public List annotations() { if (annotations == null) { @@ -231,7 +219,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 +237,7 @@ private static NullabilityData getNullabilityDataFromInheritance(Symbol.MethodSy return nullabilityData; } } - return NO_ANNOTATION_NULLABILITY[level.ordinal()]; + return noNullabilityAnnotationAt(level); } @CheckForNull @@ -302,82 +290,6 @@ 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 IAnnotationBinding annotationBinding; @@ -401,27 +313,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(JSema 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/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/JTypeSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java index 0d41214e309..20c276d2b00 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java @@ -162,12 +162,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() { 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/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/model/JMethodSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java index 23d7f0f21b4..3066697d943 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java @@ -37,6 +37,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; @@ -62,6 +63,47 @@ 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; } }"); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java index 7198d5176f4..454faa10b70 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java @@ -244,7 +244,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 +254,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 +264,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 +275,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 +286,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 +297,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 +308,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 +319,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 +330,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 +341,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/JUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java index ce220e00d0c..837dfa31d90 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java @@ -609,7 +609,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 +619,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); } From 2de30c01fc53833085bbee960beca0ab3de9fe79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Thu, 5 Nov 2026 15:49:13 +0100 Subject: [PATCH 2/8] Isolate the JDT frontend behind backend-neutral contracts Move JDT parsing and semantic implementations into org.sonar.java.jdt. Use the existing semantic and diagnostic API contracts directly, retain parser and shared utility contracts, and create environment cleanup callbacks in the JDT parser. --- .../java/checks/helpers/JParserTestUtils.java | 5 +- .../java/checks/helpers/JParserTestUtils.java | 3 +- .../helpers/SpringContextModelTestUtils.java | 3 +- .../JDTInitializerBlockSymbol.java} | 7 +- .../JDTMethodSymbol.java} | 10 +- .../java/org/sonar/java/jdt/JDTParser.java | 2926 +++++++++++++++++ .../org/sonar/java/jdt/JDTParserConfig.java | 260 ++ .../JProblem.java => jdt/JDTProblem.java} | 10 +- .../{model/JSema.java => jdt/JDTSema.java} | 66 +- .../JSymbol.java => jdt/JDTSymbol.java} | 37 +- .../JDTSymbolMetadata.java} | 44 +- .../{model/JType.java => jdt/JDTType.java} | 17 +- .../JDTTypeSymbol.java} | 17 +- .../JDTVariableSymbol.java} | 11 +- .../JWarning.java => jdt/JDTWarning.java} | 33 +- .../sonar/java/{ => jdt}/ProgressMonitor.java | 5 +- .../package-info.java} | 14 +- .../org/sonar/java/model/JLabelSymbol.java | 17 +- .../java/org/sonar/java/model/JParser.java | 2885 +--------------- .../org/sonar/java/model/JParserConfig.java | 256 +- .../org/sonar/java/model/KeywordSuper.java | 4 +- .../org/sonar/java/model/KeywordThis.java | 4 +- .../{model => jdt}/JMethodSymbolTest.java | 91 +- .../{model => jdt}/JParserConfigTest.java | 6 +- .../JParserReleasingJarsTest.java | 4 +- .../{model => jdt}/JParserSemanticTest.java | 92 +- .../java/{model => jdt}/JParserTest.java | 84 +- .../java/{model => jdt}/JProblemTest.java | 63 +- .../sonar/java/{model => jdt}/JSemaTest.java | 7 +- .../{model => jdt}/JSymbolMetadataTest.java | 11 +- .../java/{model => jdt}/JSymbolTest.java | 173 +- .../java/{model => jdt}/JTypeSymbolTest.java | 44 +- .../sonar/java/{model => jdt}/JTypeTest.java | 47 +- .../sonar/java/{model => jdt}/JUtilsTest.java | 62 +- .../{model => jdt}/JVariableSymbolTest.java | 12 +- .../java/{model => jdt}/JWarningTest.java | 57 +- .../sonar/java/jdt/JdtClassesLayoutTest.java | 50 + .../java/org/sonar/java/jdt/JdtTestUtils.java | 48 + .../java/{ => jdt}/ProgressMonitorTest.java | 3 +- .../java/{model => jdt}/SymbolsTest.java | 11 +- .../sonar/java/model/ClassesLayoutTest.java | 45 +- .../sonar/java/model/JParserTestUtils.java | 25 +- .../java/testing/ClassLayoutTestUtils.java | 40 + 43 files changed, 3926 insertions(+), 3683 deletions(-) rename java-frontend/src/main/java/org/sonar/java/{model/JInitializerBlockSymbol.java => jdt/JDTInitializerBlockSymbol.java} (95%) rename java-frontend/src/main/java/org/sonar/java/{model/JMethodSymbol.java => jdt/JDTMethodSymbol.java} (93%) create mode 100644 java-frontend/src/main/java/org/sonar/java/jdt/JDTParser.java create mode 100644 java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java rename java-frontend/src/main/java/org/sonar/java/{model/JProblem.java => jdt/JDTProblem.java} (91%) rename java-frontend/src/main/java/org/sonar/java/{model/JSema.java => jdt/JDTSema.java} (71%) rename java-frontend/src/main/java/org/sonar/java/{model/JSymbol.java => jdt/JDTSymbol.java} (92%) rename java-frontend/src/main/java/org/sonar/java/{model/JSymbolMetadata.java => jdt/JDTSymbolMetadata.java} (88%) rename java-frontend/src/main/java/org/sonar/java/{model/JType.java => jdt/JDTType.java} (95%) rename java-frontend/src/main/java/org/sonar/java/{model/JTypeSymbol.java => jdt/JDTTypeSymbol.java} (93%) rename java-frontend/src/main/java/org/sonar/java/{model/JVariableSymbol.java => jdt/JDTVariableSymbol.java} (90%) rename java-frontend/src/main/java/org/sonar/java/{model/JWarning.java => jdt/JDTWarning.java} (83%) rename java-frontend/src/main/java/org/sonar/java/{ => jdt}/ProgressMonitor.java (97%) rename java-frontend/src/main/java/org/sonar/java/{model/JPackageSymbol.java => jdt/package-info.java} (75%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JMethodSymbolTest.java (87%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JParserConfigTest.java (93%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JParserReleasingJarsTest.java (97%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JParserSemanticTest.java (96%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JParserTest.java (93%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JProblemTest.java (58%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JSemaTest.java (95%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JSymbolMetadataTest.java (97%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JSymbolTest.java (82%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JTypeSymbolTest.java (79%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JTypeTest.java (91%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JUtilsTest.java (93%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JVariableSymbolTest.java (87%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/JWarningTest.java (79%) create mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JdtClassesLayoutTest.java create mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JdtTestUtils.java rename java-frontend/src/test/java/org/sonar/java/{ => jdt}/ProgressMonitorTest.java (99%) rename java-frontend/src/test/java/org/sonar/java/{model => jdt}/SymbolsTest.java (95%) create mode 100644 java-frontend/src/test/java/org/sonar/java/testing/ClassLayoutTestUtils.java 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/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/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 93% 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 a60e528f01e..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])); } } } @@ -186,7 +186,7 @@ public boolean isParametrizedMethod() { @Nullable @Override public Object annotationElementDefaultValue() { - return isUnknown() ? null : JSymbolMetadata.convertAnnotationValue(sema, methodBinding().getDefaultValue()); + return isUnknown() ? null : JDTSymbolMetadata.convertAnnotationValue(sema, methodBinding().getDefaultValue()); } @Override 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..323cb29c9aa --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java @@ -0,0 +1,260 @@ +/* + * 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.JParserConfig.Result; +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 88% 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 e86f60bb32f..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,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.EnumMap; @@ -27,6 +27,8 @@ 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; @@ -34,18 +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 { - public static NullabilityData noNullabilityAnnotationAt(NullabilityLevel level) { - return JNullabilityData.noNullabilityAnnotationAt(level); - } - - public static NullabilityData unknownNullabilityAt(NullabilityLevel level) { - return JNullabilityData.unknownNullabilityAt(level); - } - - private final JSema sema; + private final JDTSema sema; private final Symbol symbol; private final IAnnotationBinding[] symbolBindings; private final SymbolMetadata[] parameterMetas; @@ -65,7 +61,7 @@ public static NullabilityData unknownNullabilityAt(NullabilityLevel level) { 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 @@ public static NullabilityData unknownNullabilityAt(NullabilityLevel level) { 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,31 +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); + .toArray(JDTSymbolMetadata[]::new); + return new JDTSymbolMetadata(sema, owner, symbolAnnotations, parameterMetaDatas, true); } @Override @@ -291,7 +287,7 @@ private static NullabilityTarget getTarget(Symbol symbol) { } static final class JAnnotationInstance implements AnnotationInstance { - private final JSema sema; + private final JDTSema sema; private final IAnnotationBinding annotationBinding; /** @@ -299,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; } @@ -320,7 +316,7 @@ public List values() { } } - static Object convertAnnotationValue(JSema sema, Object value) { + static Object convertAnnotationValue(JDTSema sema, Object value) { return switch (value) { case IVariableBinding iVariableBinding -> sema.variableSymbol(iVariableBinding); case ITypeBinding iTypeBinding -> sema.typeSymbol(iTypeBinding); 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 93% 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 20c276d2b00..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); } @@ -200,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 @@ -220,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/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..fb4072749e6 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,65 @@ package org.sonar.java.model; import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; -import java.util.stream.StreamSupport; -import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.FileASTRequestor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputFile; -import org.sonar.java.AnalysisException; import org.sonar.java.AnalysisProgress; -import org.sonar.java.ExecutionTimeReport; -import org.sonar.java.ProgressMonitor; -import org.sonar.java.annotations.VisibleForTesting; +import org.sonar.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(Exception exception, 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/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/test/java/org/sonar/java/model/JMethodSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JMethodSymbolTest.java similarity index 87% 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 3066697d943..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; @@ -53,13 +57,13 @@ 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 { @@ -103,19 +107,18 @@ private static Object annotationElementDefaultValue(Symbol.TypeSymbol annotation 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")))); } @@ -126,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(); @@ -168,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); @@ -239,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)); } @@ -254,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), @@ -267,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))); } @@ -277,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() @@ -299,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), @@ -313,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)); @@ -325,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(); } @@ -335,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". @@ -361,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)), @@ -369,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) @@ -382,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")); } @@ -395,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(); @@ -424,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(); @@ -436,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(); @@ -446,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(); @@ -456,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(); @@ -472,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); @@ -493,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); @@ -509,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) { @@ -557,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 97% 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 454faa10b70..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 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 837dfa31d90..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); @@ -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/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; + } +} From 91e2820bb117e46aa33e95f12dfc6f70545e82be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Thu, 5 Nov 2026 15:50:14 +0100 Subject: [PATCH 3/8] Verify JDT isolation --- .../test/java/org/sonar/java/model/AstSemanticValuesTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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/"); } } } From 88ce48c1d947a7ed659d1d3a281bdae76ce0b590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Fri, 6 Nov 2026 15:10:48 +0100 Subject: [PATCH 4/8] Address SQ issues --- .../src/main/java/org/sonar/java/jdt/JDTParserConfig.java | 1 - .../src/main/java/org/sonar/java/model/JParserConfig.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 323cb29c9aa..b4660a45ee9 100644 --- a/java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java @@ -44,7 +44,6 @@ import org.sonar.java.annotations.VisibleForTesting; import org.sonar.java.model.JParser; import org.sonar.java.model.JParserConfig; -import org.sonar.java.model.JParserConfig.Result; import org.sonar.java.model.JavaTree; import org.sonar.plugins.java.api.JavaVersion; import org.sonarsource.analyzer.commons.ProgressReport; 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 fb4072749e6..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 @@ -20,6 +20,7 @@ import java.util.List; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; +import javax.annotation.Nullable; import org.sonar.api.batch.fs.InputFile; import org.sonar.java.AnalysisProgress; import org.sonar.java.jdt.JDTParserConfig; @@ -53,7 +54,7 @@ final class Result { private final Exception exception; private final JavaTree.CompilationUnitTreeImpl tree; - private Result(Exception exception, JavaTree.CompilationUnitTreeImpl tree) { + private Result(@Nullable Exception exception, @Nullable JavaTree.CompilationUnitTreeImpl tree) { this.exception = exception; this.tree = tree; } From a44b5130e504bbcb7491bf47313c24182bb516be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 11 Nov 2026 14:28:39 +0100 Subject: [PATCH 5/8] Address review comments --- .../java/org/sonar/java/JavaFrontend.java | 2 +- .../org/sonar/java/ast/JavaAstScanner.java | 2 +- .../org/sonar/java/jdt/JDTParserConfig.java | 4 +-- .../org/sonar/java/model/JParserConfig.java | 2 +- .../org/sonar/java/utils/SpringUtils.java | 1 + .../main/resources/static/documentation.md | 26 +++++++++++++++++++ 6 files changed, 32 insertions(+), 5 deletions(-) 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..809072f92c0 100644 --- a/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java +++ b/java-frontend/src/main/java/org/sonar/java/JavaFrontend.java @@ -234,7 +234,7 @@ private void scanInBatches(BatchModeContext context, List allInputFil } } - private void scanBatch(BatchModeContext context, List batchFiles, AnalysisProgress analysisProgress) { + private void scanBatch(BatchModeContext context, List batchFiles, AnalysisProgress analysisProgress) { analysisProgress.startBatch(batchFiles.size()); Set environmentsCleaners = new HashSet<>(); JParserConfig.Mode.BATCH 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..733e19143fb 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 @@ -103,7 +103,7 @@ public void scanForTesting(Iterable inputFiles, Consumer inputFiles, Consumer modifyCompilationUnit) { - List filesNames = filterModuleInfo(inputFiles).toList(); + List filesNames = filterModuleInfo(inputFiles).collect(Collectors.toList()); AnalysisProgress analysisProgress = new AnalysisProgress(filesNames.size()); try { boolean shouldIgnoreUnnamedModuleForSplitPacakge = sonarComponents != null && 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 index b4660a45ee9..d9874b00ca9 100644 --- a/java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java +++ b/java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java @@ -125,7 +125,7 @@ static class Batch extends JDTParserConfig { } @Override - public void parse(Iterable inputFiles, BooleanSupplier isCanceled, + public void parse(Iterable inputFiles, BooleanSupplier isCanceled, AnalysisProgress analysisProgress, BiConsumer action) { List sourceFilePaths = new ArrayList<>(); @@ -204,7 +204,7 @@ private FileByFile(JavaVersion javaVersion, List classpath, boolean should } @Override - public void parse(Iterable inputFiles, BooleanSupplier isCanceled, + public void parse(Iterable inputFiles, BooleanSupplier isCanceled, AnalysisProgress analysisProgress, BiConsumer action) { boolean successfullyCompleted = false; boolean cancelled = false; 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 ed4c88d78ad..f3d0a4eb6ec 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 @@ -34,7 +34,7 @@ public interface JParserConfig { JavaTree.CompilationUnitTreeImpl parse(String unitName, String source); - void parse(Iterable inputFiles, BooleanSupplier isCanceled, + void parse(Iterable inputFiles, BooleanSupplier isCanceled, AnalysisProgress analysisProgress, BiConsumer action); enum Mode { 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 454456cfba1..714fc59d889 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,6 +26,7 @@ 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/sonar-java-plugin/src/main/resources/static/documentation.md b/sonar-java-plugin/src/main/resources/static/documentation.md index 2702ed40391..6a9ac63b75c 100644 --- a/sonar-java-plugin/src/main/resources/static/documentation.md +++ b/sonar-java-plugin/src/main/resources/static/documentation.md @@ -151,6 +151,32 @@ The tutorial [Writing Custom Java Rules 101](https://redirect.sonarsource.com/do ### API changes +#### **8.47** + +**Breaking** + +* `org.sonar.java.model.JParser` is now an interface. Its instance method `parse(String version, String unitName, String source)` returns a `JavaTree.CompilationUnitTreeImpl`. + The static helper `JParser.parse(...)` now takes a `JParser` as its first argument instead of an ECJ `ASTParser`. +* `org.sonar.java.model.JParserConfig` is now an interface. `astParser()` returns a `JParser` instead of an ECJ `ASTParser`. + Use the new `parse(String unitName, String source)` method to parse source with the configured Java version, or call `astParser().parse(version, unitName, source)` to supply the version explicitly. + The `Mode.BATCH.create(...)` and `Mode.FILE_BY_FILE.create(...)` factories and the multi-file `parse(...)` method remain available. + The multi-file `parse(...)` method now takes `Iterable` instead of `Iterable`; callers must supply an `Iterable`, and implementations must update their override signatures. This change preserves binary compatibility because type erasure is unchanged. + `JParserConfig.Result` is now final and provides `success(tree)` and `failure(exception)` factories; `get()` returns the tree or rethrows the parsing exception. +* AST node implementations no longer expose ECJ-specific bindings such as `IBinding`, `ITypeBinding`, `IMethodBinding`, and `IVariableBinding`. + Nodes store `org.sonar.plugins.java.api.semantic.Type` and `Symbol` values, including `Symbol.TypeSymbol` and `Symbol.MethodSymbol`, directly. + Custom rules accessing implementation fields such as `binding` or `typeBinding` must use the tree API's `symbol()`, `methodSymbol()`, and `symbolType()` accessors instead. + Code constructing AST nodes must use the corresponding `setSymbol(...)`, `setMethodSymbol(...)`, and `setSymbolType(...)` methods with API types. + +**Added** + +* New method: `Symbol.MethodSymbol#annotationElementDefaultValue()`. Returns an annotation element's default value, or `null` if it has none. + Values use the same representation as `SymbolMetadata.AnnotationValue#value()`: constants, type symbols, variable symbols, annotation instances, or arrays of these values. +* New beta interfaces in `org.sonar.plugins.java.api.diagnostics`: `Problem` exposes `type()` and `message()`; `Warning` extends `Problem` and adds `syntaxTree()`, which returns the associated, non-null AST node. +* New beta enum: `ProblemKind`, with `UNDEFINED_TYPE`, `PREVIEW_FEATURE_USED`, `UNUSED_IMPORT`, `REDUNDANT_CAST`, `ASSIGNMENT_HAS_NO_EFFECT`, and `MASKED_CATCH`. +* New methods on `CompilationUnitTree`: `semanticProblems()` returns an immutable `Set` for unresolved types and uses of preview features; `supportedWarningKinds()` returns the warning kinds supported by the frontend; `warnings(ProblemKind)` returns an immutable `List` in frontend encounter order. + An unsupported warning kind returns an empty list, so use `supportedWarningKinds()` to distinguish lack of support from an absence of warnings. + Custom rules can use these methods directly without casting to `JavaTree.CompilationUnitTreeImpl` or depending on ECJ diagnostic implementations. + #### **8.41** **Breaking** From 87c277c0fe19120ab9e59b773e426cb7a9e021b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 11 Nov 2026 14:56:19 +0100 Subject: [PATCH 6/8] Keep JDT agnostic tests in the model package --- ...outTest.java => JDTClassesLayoutTest.java} | 2 +- .../sonar/java/jdt/JDTMethodSymbolTest.java | 128 ++++++++ ...nfigTest.java => JDTParserConfigTest.java} | 19 +- .../sonar/java/jdt/JDTParserSemanticTest.java | 67 ++++ .../org/sonar/java/jdt/JDTParserTest.java | 260 ++++++++++++++++ ...{JProblemTest.java => JDTProblemTest.java} | 2 +- .../jdt/{JSemaTest.java => JDTSemaTest.java} | 40 +-- .../org/sonar/java/jdt/JDTSymbolTest.java | 73 +++++ .../{JdtTestUtils.java => JDTTestUtils.java} | 4 +- .../org/sonar/java/jdt/JDTTypeSymbolTest.java | 73 +++++ .../java/org/sonar/java/jdt/JDTTypeTest.java | 103 +++++++ ...{JWarningTest.java => JDTWarningTest.java} | 2 +- .../org/sonar/java/jdt/JTypeSymbolTest.java | 174 ----------- .../java/org/sonar/java/jdt/SymbolsTest.java | 6 +- .../sonar/java/model/JParserConfigTest.java | 40 +++ .../JParserReleasingJarsTest.java | 4 +- .../{jdt => model}/JParserSemanticTest.java | 288 ++++-------------- .../java/{jdt => model}/JParserTest.java | 227 +------------- .../java/org/sonar/java/model/JSemaTest.java | 71 +++++ .../{jdt => model}/JSymbolMetadataTest.java | 13 +- .../java/{jdt => model}/JSymbolTest.java | 200 +++++------- .../org/sonar/java/model/JTypeSymbolTest.java | 138 +++++++++ .../sonar/java/{jdt => model}/JTypeTest.java | 127 ++------ .../sonar/java/{jdt => model}/JUtilsTest.java | 73 ++--- .../{jdt => model}/JVariableSymbolTest.java | 14 +- .../MethodSymbolTest.java} | 247 +++++---------- 26 files changed, 1239 insertions(+), 1156 deletions(-) rename java-frontend/src/test/java/org/sonar/java/jdt/{JdtClassesLayoutTest.java => JDTClassesLayoutTest.java} (97%) create mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JDTMethodSymbolTest.java rename java-frontend/src/test/java/org/sonar/java/jdt/{JParserConfigTest.java => JDTParserConfigTest.java} (66%) create mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JDTParserSemanticTest.java create mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JDTParserTest.java rename java-frontend/src/test/java/org/sonar/java/jdt/{JProblemTest.java => JDTProblemTest.java} (99%) rename java-frontend/src/test/java/org/sonar/java/jdt/{JSemaTest.java => JDTSemaTest.java} (64%) create mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JDTSymbolTest.java rename java-frontend/src/test/java/org/sonar/java/jdt/{JdtTestUtils.java => JDTTestUtils.java} (96%) create mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JDTTypeSymbolTest.java create mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JDTTypeTest.java rename java-frontend/src/test/java/org/sonar/java/jdt/{JWarningTest.java => JDTWarningTest.java} (99%) delete mode 100644 java-frontend/src/test/java/org/sonar/java/jdt/JTypeSymbolTest.java create mode 100644 java-frontend/src/test/java/org/sonar/java/model/JParserConfigTest.java rename java-frontend/src/test/java/org/sonar/java/{jdt => model}/JParserReleasingJarsTest.java (97%) rename java-frontend/src/test/java/org/sonar/java/{jdt => model}/JParserSemanticTest.java (90%) rename java-frontend/src/test/java/org/sonar/java/{jdt => model}/JParserTest.java (76%) create mode 100644 java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java rename java-frontend/src/test/java/org/sonar/java/{jdt => model}/JSymbolMetadataTest.java (97%) rename java-frontend/src/test/java/org/sonar/java/{jdt => model}/JSymbolTest.java (78%) create mode 100644 java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java rename java-frontend/src/test/java/org/sonar/java/{jdt => model}/JTypeTest.java (68%) rename java-frontend/src/test/java/org/sonar/java/{jdt => model}/JUtilsTest.java (91%) rename java-frontend/src/test/java/org/sonar/java/{jdt => model}/JVariableSymbolTest.java (86%) rename java-frontend/src/test/java/org/sonar/java/{jdt/JMethodSymbolTest.java => model/MethodSymbolTest.java} (63%) 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 similarity index 97% rename from java-frontend/src/test/java/org/sonar/java/jdt/JdtClassesLayoutTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JDTClassesLayoutTest.java index 77bbd90167e..555ecec4f8d 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JdtClassesLayoutTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTClassesLayoutTest.java @@ -21,7 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.java.testing.ClassLayoutTestUtils.countFields; -class JdtClassesLayoutTest { +class JDTClassesLayoutTest { @Test void type() { diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JDTMethodSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTMethodSymbolTest.java new file mode 100644 index 00000000000..eb6865ed319 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTMethodSymbolTest.java @@ -0,0 +1,128 @@ +/* + * 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.util.ArrayList; +import java.util.Collection; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.sonar.java.model.JParserTestUtils; +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.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.LambdaExpressionTree; +import org.sonar.plugins.java.api.tree.VariableTree; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.sonar.java.jdt.JDTTestUtils.methodBinding; +import static org.sonar.java.jdt.JDTTestUtils.sema; + +class JDTMethodSymbolTest { + + @Test + void testBindingReturnNullSuperClass() { + CompilationUnitTreeImpl cu = test("" + + "class A { public void a() { } }"); + ClassTreeImpl a = (ClassTreeImpl) cu.types().get(0); + MethodTreeImpl m = (MethodTreeImpl) a.members().get(0); + JDTMethodSymbol symbol = (JDTMethodSymbol) m.symbol(); + + ITypeBinding iTypeBinding = mock(ITypeBinding.class); + when(iTypeBinding.getSuperclass()).thenReturn(null); + ITypeBinding[] interfaces = {null}; + when(iTypeBinding.getInterfaces()).thenReturn(interfaces); + + Collection overrides = new ArrayList<>(); + assertDoesNotThrow(() -> + symbol.findOverridesInParentTypes(overrides, methodBinding -> false, iTypeBinding)); + assertThat(overrides).isEmpty(); + } + + @Test + void support_unexpected_IMethodBinding_null_return_type() { + CompilationUnitTreeImpl cu = test("class C { C m() { return null; } }"); + ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); + MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); + IMethodBinding brokenMethodBinding = spy(JDTTestUtils.methodBinding(m)); + when(brokenMethodBinding.getReturnType()).thenReturn(null); + JDTMethodSymbol methodSymbol = new JDTMethodSymbol(sema(cu), brokenMethodBinding); + assertThat(methodSymbol.returnType().isUnknown()).isTrue(); + assertThat(methodSymbol.isNativeMethod()).isFalse(); + assertThat(methodSymbol.metadata()).isSameAs(Symbols.EMPTY_METADATA); + } + + @Test + void isLambda() { + CompilationUnitTreeImpl cu = test(""" + class A { + void foo() { } + java.util.function.IntConsumer bar = i -> { }; + } + """); + ClassTreeImpl a = (ClassTreeImpl) cu.types().get(0); + + MethodTreeImpl foo = (MethodTreeImpl) a.members().get(0); + 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 = (JDTMethodSymbol) initializer.symbol(); + assertTrue(lambdaSymbol.isLambda()); + assertThat(lambdaSymbol.toString()).isNotEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = { + "class C { C m() { return null; } }", + "class C { C m() { return null; } }", + "interface I { default C m() { return null; } }", + "class C { synchronized C m() { return null; } }", + "class C { C m(String... s) { return null; } }" + }) + void recovered_method_modifiers(String source) { + CompilationUnitTreeImpl cu = test(source); + ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); + MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); + IMethodBinding binding = spy(methodBinding(m)); + when(binding.getReturnType()).thenReturn(null); + when(binding.isRecovered()).thenReturn(true); + JDTMethodSymbol symbol = new JDTMethodSymbol(sema(cu), binding); + + assertThat(symbol.isOverridable()).isFalse(); + assertThat(symbol.isParametrizedMethod()).isFalse(); + assertThat(symbol.isDefaultMethod()).isFalse(); + assertThat(symbol.isSynchronizedMethod()).isFalse(); + assertThat(symbol.isVarArgsMethod()).isFalse(); + } + + private static CompilationUnitTreeImpl test(String source) { + return (CompilationUnitTreeImpl) JParserTestUtils.parse(source); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JParserConfigTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserConfigTest.java similarity index 66% rename from java-frontend/src/test/java/org/sonar/java/jdt/JParserConfigTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JDTParserConfigTest.java index adaeeed31c5..d3457d0c9b5 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JParserConfigTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserConfigTest.java @@ -16,20 +16,13 @@ */ 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.jdt.JDTParserConfig.shouldEnablePreviewFlag; -class JParserConfigTest { - @RegisterExtension - public ThreadLocalLogTester logTester = new ThreadLocalLogTester().setLevel(Level.INFO); +class JDTParserConfigTest { @Test void should_enable_preview() { @@ -47,14 +40,4 @@ void should_enable_preview() { assertThat(shouldEnablePreviewFlag(JavaVersionImpl.fromString("1.8"))).isFalse(); assertThat(shouldEnablePreviewFlag(JavaVersionImpl.fromString("1.8", "True"))).isTrue(); } - - @Test - void a_debug_message_is_logged_when_shouldIgnoreUnnamedModuleForSplitPackage_is_set() { - JParserConfig.Mode.BATCH.create(new JavaVersionImpl(17), Collections.emptyList()); - assertThat(logTester.logs()).isEmpty(); - JParserConfig.Mode.BATCH.create(new JavaVersionImpl(17), Collections.emptyList(), false); - assertThat(logTester.logs()).isEmpty(); - JParserConfig.Mode.BATCH.create(new JavaVersionImpl(17), Collections.emptyList(), true); - assertThat(logTester.logs()).containsExactly("The Java analyzer will ignore the unnamed module for split packages."); - } } diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserSemanticTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserSemanticTest.java new file mode 100644 index 00000000000..a53588dd5ab --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserSemanticTest.java @@ -0,0 +1,67 @@ +/* + * 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.util.HashMap; +import java.util.Map; +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.Block; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SwitchStatement; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.junit.jupiter.api.Test; +import org.sonar.java.model.JParserConfig; +import org.sonar.plugins.java.api.JavaVersion; + +import static org.assertj.core.api.Assertions.assertThat; + +class JDTParserSemanticTest { + + @Test + void not_need_to_skip_implicit_break_statement() { + String source = "class C { void m() { switch (0) { case 0 -> { } } } }"; + CompilationUnit cu = createAST(source); + TypeDeclaration c = (TypeDeclaration) cu.types().get(0); + MethodDeclaration m = c.getMethods()[0]; + SwitchStatement s = (SwitchStatement) m.getBody().statements().get(0); + Block block = (Block) s.statements().get(1); + assertThat(block.statements()).isEmpty(); + } + + private CompilationUnit createAST(String source) { + JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; + ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); + Map options = new HashMap<>(JavaCore.getOptions()); + JavaCore.setComplianceOptions(version.effectiveJavaVersionAsString(), options); + options.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled"); + astParser.setCompilerOptions(options); + astParser.setEnvironment( + new String[]{}, + new String[]{}, + new String[]{}, + true + ); + astParser.setUnitName("File.java"); + astParser.setResolveBindings(true); + astParser.setBindingsRecovery(true); + astParser.setSource(source.toCharArray()); + return (CompilationUnit) astParser.createAST(null); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserTest.java new file mode 100644 index 00000000000..f6e36146be3 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserTest.java @@ -0,0 +1,260 @@ +/* + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +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.internal.compiler.parser.TerminalToken; +import org.eclipse.jdt.internal.formatter.Token; +import org.eclipse.jdt.internal.formatter.TokenManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.event.Level; +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.JParserConfig.Result; +import org.sonar.java.model.JavaTree; +import org.sonar.java.testing.ThreadLocalLogTester; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +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.JParserTestUtils.DEFAULT_CLASSPATH; +import static org.sonar.java.model.JParserTestUtils.parse; + +class JDTParserTest { + + @RegisterExtension + public ThreadLocalLogTester logTester = new ThreadLocalLogTester().setLevel(Level.DEBUG); + + @Test + void test_first_index_of_tokens_in_eclipse_ast() { + String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); + String unitName = "C.java"; + String source = "class C { }"; + + ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); + JavaCore.setComplianceOptions(version, JavaCore.getOptions()); + astParser.setResolveBindings(true); + astParser.setUnitName(unitName); + astParser.setSource(source.toCharArray()); + CompilationUnit compilationUnit = (CompilationUnit) astParser.createAST(null); + TokenManager tokenManager = JDTParser.createTokenManager(version, unitName, source); + + 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"); + } + + @Test + void test_comment_tokens() { + String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); + String unitName = "C.java"; + String source = """ + class A { + // line comment + /* block comment */ + /// markdown comment 1 + /// markdown comment 2 + /** + * javadoc comment + */ + void foo() {} + } + """; + TokenManager tokens = JDTParser.createTokenManager(version, unitName, source); + + assertThat(tokens.size()).isEqualTo(15); + + Token token0 = tokens.get(0); + assertThat(token0.toString(source)).isEqualTo("class"); + assertThat(token0.isComment()).isFalse(); + assertThat(isComment(token0)).isFalse(); + assertThatThrownBy(() -> convertTokenTypeToCommentKind(token0)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Unexpected value: TokenNameclass"); + + Token token3 = tokens.get(3); + assertThat(token3.toString(source)).isEqualTo("// line comment"); + assertThat(token3.isComment()).isTrue(); + assertThat(isComment(token3)).isTrue(); + assertThat(convertTokenTypeToCommentKind(token3)).isEqualTo(CommentKind.LINE); + + Token token4 = tokens.get(4); + assertThat(token4.toString(source)).isEqualTo("/* block comment */"); + assertThat(token4.isComment()).isTrue(); + assertThat(isComment(token4)).isTrue(); + assertThat(convertTokenTypeToCommentKind(token4)).isEqualTo(CommentKind.BLOCK); + + Token token5 = tokens.get(5); + assertThat(token5.toString(source)).isEqualTo("/// markdown comment 1\n /// markdown comment 2"); + assertThat(token5.isComment()).isTrue(); + assertThat(isComment(token5)).isTrue(); + assertThat(convertTokenTypeToCommentKind(token5)).isEqualTo(CommentKind.MARKDOWN); + + Token token6 = tokens.get(6); + assertThat(token6.toString(source)).isEqualTo("/**\n * javadoc comment\n */"); + assertThat(token6.isComment()).isTrue(); + assertThat(isComment(token6)).isTrue(); + assertThat(convertTokenTypeToCommentKind(token6)).isEqualTo(CommentKind.JAVADOC); + + Token token7 = tokens.get(7); + assertThat(token7.toString(source)).isEqualTo("void"); + assertThat(token7.isComment()).isFalse(); + assertThat(isComment(token7)).isFalse(); + } + + @Test + void failing_batch_mode_should_continue_file_by_file() { + List inputFiles = Arrays.asList( + TestUtils.inputFile("src/test/files/metrics/Classes.java"), + TestUtils.inputFile("src/test/files/metrics/Methods.java")); + List trace = new ArrayList<>(); + BiConsumer action = (inputFile, result) -> { + trace.addAll(logTester.logs()); + logTester.clear(); + try { + JavaTree.CompilationUnitTreeImpl tree = result.get(); + trace.add("[action] analyse class " + ((ClassTree)tree.types().get(0)).simpleName().name() + " in " + inputFile.filename()); + } catch (Exception e) { + trace.add("[action] handle " + e.getClass().getSimpleName() + ": " + e.getMessage()); + } + }; + BatchWithException batchWithException = new BatchWithException(); + // exception before the first file + batchWithException.exceptions.push(new NullPointerException("Boom!")); + batchWithException.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), action); + trace.addAll(logTester.logs()); + logTester.clear(); + assertThat(trace).containsExactly( + "Unexpected NullPointerException: Boom!", + "Fallback to file by file analysis for 2 files", + "[action] analyse class HelloWorld in Classes.java", + "[action] analyse class MyClass in Methods.java"); + } + + @Test + void successful_batch_mode_with_missing_analyzed_files_should_continue_file_by_file() { + List inputFiles = Arrays.asList( + TestUtils.inputFile("src/test/files/metrics/Classes.java"), + TestUtils.inputFile("src/test/files/metrics/Methods.java")); + 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.jdtParser()).thenReturn(astParser); + + config.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), doNothingAction); + + assertThat(logTester.logs()).containsExactly( + "Unexpected AnalysisException: 2/2 files were not analyzed by the batch mode", + "Fallback to file by file analysis for 2 files"); + logTester.clear(); + } + + @Test + void failing_batch_mode_should_stop_file_by_file_when_cancelled() { + List inputFiles = Arrays.asList( + TestUtils.inputFile("src/test/files/metrics/Classes.java"), + TestUtils.inputFile("src/test/files/metrics/Methods.java")); + List trace = new ArrayList<>(); + AtomicBoolean isCanceled = new AtomicBoolean(false); + BiConsumer action = (inputFile, result) -> { + trace.addAll(logTester.logs()); + logTester.clear(); + try { + JavaTree.CompilationUnitTreeImpl tree = result.get(); + trace.add("[action] analyze class " + ((ClassTree)tree.types().get(0)).simpleName().name() + " in " + inputFile.filename()); + // cancel analysis after first file + isCanceled.set(true); + } catch (Exception e) { + trace.add("[action] handle " + e.getClass().getSimpleName() + ": " + e.getMessage()); + } + }; + + JParserConfig config = BATCH + .create(MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), false); + AnalysisProgress analysisProgress = new AnalysisProgress(inputFiles.size()); + assertThatThrownBy(() -> config.parse(inputFiles, isCanceled::get, analysisProgress, action)) + .isInstanceOf(OperationCanceledException.class); + + trace.addAll(logTester.logs()); + logTester.clear(); + assertThat(trace).containsExactly( + "Starting batch processing.", + "[action] analyze class HelloWorld in Classes.java", + "Batch processing: Cancelled!"); + // Missing: [action] analyse class MyClass in Methods.java + } + + @Test + 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 "); + BatchWithException batchWithException = new BatchWithException(); + // exception before the first file + batchWithException.exceptions.push(new NullPointerException("Boom!")); + batchWithException.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), action); + trace.addAll(logTester.logs()); + logTester.clear(); + assertThat(trace).containsExactly( + "Unexpected NullPointerException: Boom!"); + } + + static class BatchWithException extends JDTParserConfig.Batch { + + Deque exceptions = new LinkedList<>(); + + public BatchWithException() { + super(MAXIMUM_SUPPORTED_JAVA_VERSION, DEFAULT_CLASSPATH, false); + } + + @Override + public ASTParser jdtParser() { + if (!exceptions.isEmpty()) { + throw exceptions.pop(); + } + return super.jdtParser(); + } + + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JProblemTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTProblemTest.java similarity index 99% rename from java-frontend/src/test/java/org/sonar/java/jdt/JProblemTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JDTProblemTest.java index 8dc08131ebb..34e42935da7 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JProblemTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTProblemTest.java @@ -32,7 +32,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class JProblemTest { +class JDTProblemTest { @Test void normalizes_preview_messages_at_construction() { diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JSemaTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTSemaTest.java similarity index 64% rename from java-frontend/src/test/java/org/sonar/java/jdt/JSemaTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JDTSemaTest.java index 932662fb0c0..220798b7aa3 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JSemaTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTSemaTest.java @@ -24,20 +24,10 @@ 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; -import static org.junit.jupiter.api.Assertions.assertAll; -class JSemaTest { - - @Test - void testGetClassTypeUnknown() { - assertThat(sema.getClassType("com.google.common.collect.ImmutableMap")). - isEqualTo(Type.UNKNOWN); - assertThat(sema.getClassType("java.lang.Object")) - .isNotEqualTo(Type.UNKNOWN); - } +class JDTSemaTest { @Test void type() { @@ -47,33 +37,6 @@ void type() { .isSameAs(sema.type(typeBinding)); } - @Test - void resolveType() { - assertAll( - () -> - assertThat(sema.resolveType("Nonexistent")) - .isNull(), - () -> - assertThat(sema.resolveType("void")) - .isNotNull(), - () -> - assertThat(sema.resolveType("java.util.Map")) - .isNotNull(), - () -> - assertThat(sema.resolveType("java.util.Map$Entry")) - .isNotNull(), - () -> - assertThat(sema.resolveType("java.util.Map$Entry[][]")) - .isNotNull() - ); - } - - @Test - void resolvePackageAnnotations() { - assertThat(sema.resolvePackageAnnotations("org.sonar.java.resolve.targets")) - .hasSize(1); - } - private JDTSema sema; @BeforeEach @@ -93,5 +56,4 @@ void setup() { AST ast = astParser.createAST(null).getAST(); sema = new JDTSema(ast); } - } diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JDTSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTSymbolTest.java new file mode 100644 index 00000000000..7a4c44afda4 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTSymbolTest.java @@ -0,0 +1,73 @@ +/* + * 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.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +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.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.BlockTree; +import org.sonar.plugins.java.api.tree.VariableTree; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.java.jdt.JDTTestUtils.sema; +import static org.sonar.java.model.assertions.SymbolAssert.assertThat; + +class JDTSymbolTest { + + @Test + 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); + JDTSema jSema = new JDTSema(cu.getAST()); + IVariableBinding mock = mock(IVariableBinding.class); + when(mock.getKind()).thenReturn(IBinding.VARIABLE); + JDTVariableSymbol jVariableSymbol = new JDTVariableSymbol(jSema, mock); + assertThat(jVariableSymbol.metadata()).isNotNull(); + } + + @Test + void null_package_binding_is_the_root_package() { + assertThat(sema(test("")).packageSymbol(null)).isEqualTo(Symbol.ROOT_PACKAGE); + } + + @Test + void initializer_symbols_are_cached() { + var cu = test("class C { static { int a; } { int b; } }"); + var c = (ClassTreeImpl) cu.types().get(0); + var staticBlock = (BlockTree) c.members().get(0); + var block = (BlockTree) c.members().get(1); + var a = (VariableTree) staticBlock.body().get(0); + var b = (VariableTree) block.body().get(0); + + assertThat(a.symbol().owner()).isSameAs(sema(cu).staticInitializerBlockSymbol((JDTTypeSymbol) c.symbol())); + assertThat(b.symbol().owner()).isSameAs(sema(cu).initializerBlockSymbol((JDTTypeSymbol) c.symbol())); + } + + private static JavaTree.CompilationUnitTreeImpl test(String source) { + return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); + } +} 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 similarity index 96% rename from java-frontend/src/test/java/org/sonar/java/jdt/JdtTestUtils.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JDTTestUtils.java index 9e8b387c606..eaedd7d5810 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JdtTestUtils.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTTestUtils.java @@ -24,9 +24,9 @@ import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.MethodTree; -final class JdtTestUtils { +final class JDTTestUtils { - private JdtTestUtils() { + private JDTTestUtils() { } static JDTSema sema(CompilationUnitTree tree) { diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JDTTypeSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTTypeSymbolTest.java new file mode 100644 index 00000000000..d332c2c9f67 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTTypeSymbolTest.java @@ -0,0 +1,73 @@ +/* + * 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.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.plugins.java.api.semantic.Type; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.sonar.java.jdt.JDTTestUtils.sema; +import static org.sonar.java.jdt.JDTTestUtils.typeBinding; + +class JDTTypeSymbolTest { + + @Test + void testSuperClassWhenSemanticCanNotResolveObjectType() { + var cu = test("interface I{}"); + var i = (ClassTreeImpl) cu.types().get(0); + + JDTSema sematicThatCanNotResolveObjectType = spy(sema(cu)); + when(sematicThatCanNotResolveObjectType.resolveType("java.lang.Object")).thenReturn(null); + var typeSymbol = new JDTTypeSymbol(sematicThatCanNotResolveObjectType, typeBinding(i)); + assertThat(typeSymbol.superClass()).isSameAs(Type.UNKNOWN); + } + + 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 JDTTypeSymbol(sema(cu), brokenTypeBinding); + } + return new JDTTypeSymbol(sema(cu), typeBinding(c)); + } + + private static JavaTree.CompilationUnitTreeImpl test(String source) { + return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); + } + + @Test + void recovered_type_has_no_super_types() { + var cu = test("class C {}"); + var c = (ClassTreeImpl) cu.types().get(0); + ITypeBinding binding = spy(typeBinding(c)); + when(binding.isRecovered()).thenReturn(true); + assertThat(new JDTTypeSymbol(sema(cu), binding).superTypes()).isEmpty(); + } + + @Test + void recovered_annotation_is_not_an_annotation() { + assertThat(getJTypeSymbolFromClassText("@interface A {}", true).isAnnotation()).isFalse(); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JDTTypeTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTTypeTest.java new file mode 100644 index 00000000000..84c93ff4f19 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTTypeTest.java @@ -0,0 +1,103 @@ +/* + * 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.util.Objects; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.event.Level; +import org.sonar.java.testing.ThreadLocalLogTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.sonar.java.model.assertions.TypeAssert.assertThat; + +class JDTTypeTest { + + @RegisterExtension + public ThreadLocalLogTester logTester = new ThreadLocalLogTester().setLevel(Level.DEBUG); + + @Test + void primitiveWrapperType_returns_null_when_semantic_cannot_resolve_wrapper_type() { + // Sema that can resolve primitives, but fails to resolve wrapper types, which can happen when the classpath is not correctly set up + var brokenSema = spy(sema); + when(brokenSema.resolveType("java.lang.Byte")).thenReturn(null); + + var primitiveBinding = sema.resolveType("byte"); + var primitiveType = new JDTType(brokenSema, primitiveBinding); + + assertThat(primitiveType.primitiveWrapperType()).isNull(); + } + + @Test + void primitiveType_returns_null_when_semantic_cannot_resolve_primitive_type() { + // Sema that can resolve wrapper types, but fails to resolve a primitive, this should never happen in practice, + // but we want to make sure it doesn't throw an exception and returns null instead + var brokenSema = spy(sema); + when(brokenSema.resolveType("byte")).thenReturn(null); + + var wrapperBinding = sema.resolveType("java.lang.Byte"); + var wrapperType = new JDTType(brokenSema, wrapperBinding); + + assertThat(wrapperType.primitiveType()).isNull(); + } + + @Test + void is_subtype_of_should_not_throw_NPE() { + 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")); + + JDTType stringType = new JDTType(sema, brokenStringBinding); + // should catch the NullPointerException + assertThat(stringType.isSubtypeOf(objectType)).isFalse(); + + assertThat(logTester.logs(Level.DEBUG)) + .containsExactly("NullPointerException while resolving isSubTypeCompatible()"); + } + + private JDTType type(String name) { + ITypeBinding typeBinding = Objects.requireNonNull(sema.resolveType(name)); + return sema.type(typeBinding); + } + + private JDTSema sema; + + @BeforeEach + void setup() { + ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); + astParser.setEnvironment( + new String[]{}, + new String[]{}, + new String[]{}, + true + ); + astParser.setResolveBindings(true); + astParser.setUnitName("File.java"); + astParser.setSource("".toCharArray()); + AST ast = astParser.createAST(null).getAST(); + sema = new JDTSema(ast); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JWarningTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTWarningTest.java similarity index 99% rename from java-frontend/src/test/java/org/sonar/java/jdt/JWarningTest.java rename to java-frontend/src/test/java/org/sonar/java/jdt/JDTWarningTest.java index 27e08e0bffa..2ab34f2c5c1 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JWarningTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTWarningTest.java @@ -47,7 +47,7 @@ import static org.sonar.java.jdt.JDTWarning.Mapper.matchesTreeExactly; import static org.sonar.java.jdt.JDTWarning.Mapper.setSyntaxTree; -class JWarningTest { +class JDTWarningTest { @ParameterizedTest @CsvSource({"7, 2, 5", "13, 3, 4"}) diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JTypeSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JTypeSymbolTest.java deleted file mode 100644 index 0b7b88e2687..00000000000 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JTypeSymbolTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * SonarQube Java - * Copyright (C) SonarSource Sàrl - * mailto:info AT sonarsource DOT com - * - * You can redistribute and/or modify this program under the terms of - * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the Sonar Source-Available License for more details. - * - * You should have received a copy of the Sonar Source-Available License - * along with this program; if not, see https://sonarsource.com/license/ssal/ - */ -package org.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; -import org.sonar.plugins.java.api.semantic.Type; - -import static org.assertj.core.api.Assertions.assertThat; -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.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.typeOf; - -class JTypeSymbolTest { - - @Test - void superClass() { - JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { } class C2 extends C { }"); - ITypeBinding javaLangObject = Objects.requireNonNull(sema(cu).resolveType("java.lang.Object")); - ITypeBinding i = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(0)))); - ITypeBinding c = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(1)))); - ITypeBinding c2 = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(2)))); - assertAll( - // for java.lang.Object - () -> - assertThat(sema(cu).typeSymbol(javaLangObject).superClass()) - .isNull(), - // for interfaces - () -> - assertThat(sema(cu).typeSymbol(i).superClass()) - .isSameAs(sema(cu).type(javaLangObject)), - // for classes - () -> - assertThat(sema(cu).typeSymbol(c).superClass()) - .isSameAs(sema(cu).type(javaLangObject)), - () -> - assertThat(sema(cu).typeSymbol(c2).superClass()) - .isSameAs(sema(cu).type(c)), - // for arrays - () -> - assertThat(sema(cu).typeSymbol(javaLangObject.createArrayType(1)).superClass()) - .isSameAs(sema(cu).type(javaLangObject)) - ); - } - - @Test - void testSuperClassWhenSemanticCanNotResolveObjectType() { - var cu = test("interface I{}"); - var i = (ClassTreeImpl) cu.types().get(0); - - JDTSema sematicThatCanNotResolveObjectType = spy(sema(cu)); - when(sematicThatCanNotResolveObjectType.resolveType("java.lang.Object")).thenReturn(null); - var typeSymbol = new JDTTypeSymbol(sematicThatCanNotResolveObjectType, typeBinding(i)); - assertThat(typeSymbol.superClass()).isSameAs(Type.UNKNOWN); - } - - @Test - void interfaces() { - JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { }"); - ITypeBinding i = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(0)))); - ITypeBinding c = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(1)))); - assertThat(sema(cu).typeSymbol(i).interfaces()) - .isEmpty(); - assertThat(sema(cu).typeSymbol(c).interfaces()) - .containsOnly(sema(cu).type(i)); - } - - @Test - void memberSymbols() { - JavaTree.CompilationUnitTreeImpl cu = test("class C { int f; C() {} class N {} }"); - ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); - VariableTreeImpl f = (VariableTreeImpl) c.members().get(0); - MethodTreeImpl m = (MethodTreeImpl) c.members().get(1); - ClassTreeImpl n = (ClassTreeImpl) c.members().get(2); - assertThat(((JDTTypeSymbol) typeOf(c).symbol()).memberSymbols()) - .containsOnly( - ((JDTVariableSymbol) symbolOf(f)), - ((JDTMethodSymbol) symbolOf(m)), - ((JDTTypeSymbol) typeOf(n).symbol()) - ); - } - - @Test - void superTypesTest(){ - JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { } class C2 extends C { }"); - ITypeBinding javaLangObject = Objects.requireNonNull(sema(cu).resolveType("java.lang.Object")); - assertThat(sema(cu).typeSymbol(javaLangObject).superTypes()).isEmpty(); - - ClassTreeImpl interfaceI = (ClassTreeImpl) cu.types().get(0); - 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); - JDTTypeSymbol classCTypeSymbol = ((JDTTypeSymbol) typeOf(classC).symbol()); - assertThat(classCTypeSymbol.superTypes()).containsExactly(interfaceITypeSymbol.type(), sema(cu).type(javaLangObject)); - - ClassTreeImpl classC2 = (ClassTreeImpl) cu.types().get(2); - 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); - JDTTypeSymbol brokenTypeSymbol = new JDTTypeSymbol(sema(cu), brokenTypeBinding); - assertThat(brokenTypeSymbol.superTypes()).isEmpty(); - } - - @Test - void outermostClassTest() { - JavaTree.CompilationUnitTreeImpl cu = test("class C { class N {} }"); - ClassTreeImpl outerClass = (ClassTreeImpl) cu.types().get(0); - ClassTreeImpl innerClass = (ClassTreeImpl) outerClass.members().get(0); - 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(){ - JDTTypeSymbol annotationSymbol = getJTypeSymbolFromClassText("@interface A { }", true); - assertThat(annotationSymbol.isAnnotation()).isFalse(); - annotationSymbol = getJTypeSymbolFromClassText("@interface A { }", false); - assertThat(annotationSymbol.isAnnotation()).isTrue(); - JDTTypeSymbol classSymbol = getJTypeSymbolFromClassText("class C { }", false); - assertThat(classSymbol.isAnnotation()).isFalse(); - } - - @Test - void isEffectivelyFinalTest(){ - JDTTypeSymbol classSymbol = getJTypeSymbolFromClassText("class C { }", false); - assertThat(classSymbol.superSymbol.isEffectivelyFinal()).isFalse(); - } - - 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 JDTTypeSymbol(sema(cu), brokenTypeBinding); - } - return new JDTTypeSymbol(sema(cu), typeBinding(c)); - } - - private static JavaTree.CompilationUnitTreeImpl test(String source) { - return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); - } - -} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/SymbolsTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/SymbolsTest.java index 299538d4631..0f486cd1a4a 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/SymbolsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/SymbolsTest.java @@ -39,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.jdt.JdtTestUtils.sema; +import static org.sonar.java.jdt.JDTTestUtils.sema; class SymbolsTest { @@ -108,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 JDTSymbolMetadata(JUtilsTest.SEMA, symbol, new IAnnotationBinding[0]); + SymbolMetadata metadata = new JDTSymbolMetadata(sema(JParserTestUtils.parse("")), 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); @@ -207,7 +207,7 @@ private static void assertCommonProperties(Symbol unknownSymbol) { @Test void testIsAnnotation() { - JDTSema sema = sema(((JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(""))); + JDTSema sema = sema(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/JParserConfigTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserConfigTest.java new file mode 100644 index 00000000000..a03ca80e012 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserConfigTest.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.model; + +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.testing.ThreadLocalLogTester; + +import static org.assertj.core.api.Assertions.assertThat; + +class JParserConfigTest { + @RegisterExtension + public ThreadLocalLogTester logTester = new ThreadLocalLogTester().setLevel(Level.INFO); + + @Test + void a_debug_message_is_logged_when_shouldIgnoreUnnamedModuleForSplitPackage_is_set() { + JParserConfig.Mode.BATCH.create(new JavaVersionImpl(17), Collections.emptyList()); + assertThat(logTester.logs()).isEmpty(); + JParserConfig.Mode.BATCH.create(new JavaVersionImpl(17), Collections.emptyList(), false); + assertThat(logTester.logs()).isEmpty(); + JParserConfig.Mode.BATCH.create(new JavaVersionImpl(17), Collections.emptyList(), true); + assertThat(logTester.logs()).containsExactly("The Java analyzer will ignore the unnamed module for split packages."); + } +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JParserReleasingJarsTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java similarity index 97% rename from java-frontend/src/test/java/org/sonar/java/jdt/JParserReleasingJarsTest.java rename to java-frontend/src/test/java/org/sonar/java/model/JParserReleasingJarsTest.java index ecfa2546b3b..d6c2207db62 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JParserReleasingJarsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/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.jdt; +package org.sonar.java.model; import com.google.common.io.Files; import java.io.File; @@ -23,8 +23,6 @@ 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/jdt/JParserSemanticTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java similarity index 90% rename from java-frontend/src/test/java/org/sonar/java/jdt/JParserSemanticTest.java rename to java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java index df5b59a01dc..e9f2f75fab4 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JParserSemanticTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java @@ -14,33 +14,16 @@ * 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; +package org.sonar.java.model; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; -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.Block; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.SwitchStatement; -import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Nested; 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; @@ -64,7 +47,6 @@ import org.sonar.java.model.statement.StaticInitializerTreeImpl; import org.sonar.java.model.statement.SwitchExpressionTreeImpl; import org.sonar.java.model.statement.YieldStatementTreeImpl; -import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.diagnostics.ProblemKind; import org.sonar.plugins.java.api.diagnostics.Warning; import org.sonar.plugins.java.api.location.Position; @@ -104,7 +86,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.sonar.java.model.JParserTestUtils.symbolOf; import static org.sonar.java.model.JParserTestUtils.typeOf; import static org.sonar.java.model.assertions.TreeAssert.assertThat; @@ -132,9 +113,6 @@ void expression_literal() { .isSameAs(typeOf(e)); } - /** - * @see org.eclipse.jdt.core.dom.InfixExpression#extendedOperands() - */ @Test void extended_operands() { BinaryExpressionTreeImpl e = (BinaryExpressionTreeImpl) expression("1 - 2 - 3"); @@ -143,9 +121,6 @@ void extended_operands() { assertThat(typeOf(leftOperand)).isSameAs(typeOf(e)); } - /** - * @see org.eclipse.jdt.core.dom.SimpleName - */ @Test void expression_simple_name() { JavaTree.CompilationUnitTreeImpl cu = test("class C { int f; Object m() { return f; } }"); @@ -157,9 +132,6 @@ void expression_simple_name() { assertThat(symbolOf(i).usages()).containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.QualifiedName - */ @Test void expression_qualified_name() { JavaTree.CompilationUnitTreeImpl cu = test("class C { Object m() { return java.lang.System.out; } }"); @@ -171,7 +143,7 @@ void expression_qualified_name() { MemberSelectExpressionTreeImpl javaLangSystem = (MemberSelectExpressionTreeImpl) javaLangSystemOut.expression(); assertThat(typeOf(javaLangSystem)).isNotSameAs(Type.UNKNOWN); - assertThat(((JDTType) typeOf(javaLangSystem)).is("java.lang.System")) + Assertions.assertThat(typeOf(javaLangSystem).is("java.lang.System")) .isEqualTo(javaLangSystem.symbolType().is("java.lang.System")) .isTrue(); @@ -195,14 +167,11 @@ void expression_qualified_name() { .isNotNull() .isSameAs(typeOf(system).symbol()); assertThat(symbolOf(system).isTypeSymbol()).isTrue(); - assertThat(((JDTType) typeOf(system)).is("java.lang.System")) + assertThat(typeOf(system).is("java.lang.System")) .isEqualTo(system.symbolType().is("java.lang.System")) .isTrue(); } - /** - * @see org.eclipse.jdt.core.dom.ThisExpression - */ @Test void expression_this() { JavaTree.CompilationUnitTreeImpl cu = test("class C { Object m() { return this; } }"); @@ -213,15 +182,12 @@ void expression_this() { assertThat(typeOf(keywordThis)).isNotSameAs(Type.UNKNOWN) .isSameAs(typeOf(Objects.requireNonNull((ClassTreeImpl) keywordThis.symbolType().symbol().declaration()))) .isSameAs(typeOf(c)); - assertThat(keywordThis.symbol()) - .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).thisSymbol); + assertThat(keywordThis.symbol().owner()).isSameAs(c.symbol()); + assertThat(keywordThis.symbol().type()).isSameAs(keywordThis.symbolType()); assertThat(keywordThis.symbol().isVariableSymbol()) .isTrue(); } - /** - * @see org.eclipse.jdt.core.dom.ThisExpression - */ @Test void expression_this_qualified() { JavaTree.CompilationUnitTreeImpl cu = test("class C1 { class C2 { Object m() { return C1.this; } } }"); @@ -237,15 +203,12 @@ void expression_this_qualified() { assertThat(typeOf(keywordThis)).isNotSameAs(Type.UNKNOWN) .isSameAs(typeOf(Objects.requireNonNull((ClassTreeImpl) keywordThis.symbolType().symbol().declaration()))) .isSameAs(typeOf(c1)); - assertThat(keywordThis.symbol()) - .isSameAs(((JDTTypeSymbol) typeOf(c1).symbol()).thisSymbol); + assertThat(keywordThis.symbol().owner()).isSameAs(c1.symbol()); + assertThat(keywordThis.symbol().type()).isSameAs(keywordThis.symbolType()); assertThat(keywordThis.symbol().isVariableSymbol()) .isTrue(); } - /** - * @see org.eclipse.jdt.core.dom.FieldAccess - */ @Test void expression_field_access() { JavaTree.CompilationUnitTreeImpl cu = test("class C { int f; Object m() { return this.f; } }"); @@ -267,9 +230,6 @@ void expression_field_access() { .containsOnly(identifier); } - /** - * @see org.eclipse.jdt.core.dom.SuperFieldAccess - */ @Test void expression_super_field_access() { JavaTree.CompilationUnitTreeImpl cu = test("class C extends S { Object m() { return super.f; } } class S { int f; }"); @@ -283,8 +243,8 @@ void expression_super_field_access() { KeywordSuper keywordSuper = (KeywordSuper) expression.expression(); assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); - assertThat(keywordSuper.symbol()) - .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).superSymbol); + assertThat(keywordSuper.symbol().owner()).isSameAs(c.symbol()); + assertThat(keywordSuper.symbol().type()).isSameAs(keywordSuper.symbolType()); IdentifierTreeImpl identifier = (IdentifierTreeImpl) expression.identifier(); assertThat(symbolOf(identifier)) @@ -296,9 +256,6 @@ void expression_super_field_access() { .containsOnly(identifier); } - /** - * @see org.eclipse.jdt.core.dom.SuperFieldAccess - */ @Test void expression_super_field_access_qualified() { JavaTree.CompilationUnitTreeImpl cu = test("class T extends S { class C { Object m() { return T.super.f; } } } class S { int f; }"); @@ -319,8 +276,8 @@ void expression_super_field_access_qualified() { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(Objects.requireNonNull((ClassTreeImpl) keywordSuper.symbolType().symbol().declaration())) .isSameAs(superClass); - assertThat(keywordSuper.symbol()) - .isSameAs(((JDTTypeSymbol) typeOf(t).symbol()).superSymbol); + assertThat(keywordSuper.symbol().owner()).isSameAs(t.symbol()); + assertThat(keywordSuper.symbol().type()).isSameAs(keywordSuper.symbolType()); IdentifierTreeImpl identifier = (IdentifierTreeImpl) expression.identifier(); assertThat(symbolOf(identifier)) @@ -332,9 +289,6 @@ void expression_super_field_access_qualified() { .containsOnly(identifier); } - /** - * @see org.eclipse.jdt.core.dom.TypeLiteral - */ @Test void expression_type_literal() { JavaTree.CompilationUnitTreeImpl cu = test("class C { Object m() { return C.class; } }"); @@ -352,9 +306,6 @@ void expression_type_literal() { .isTrue(); } - /** - * @see org.eclipse.jdt.core.dom.ClassInstanceCreation - */ @Test void expression_class_instance_creation() { JavaTree.CompilationUnitTreeImpl cu = test("class C { Object m() { return new C(); } C(){} }"); @@ -376,9 +327,6 @@ void expression_class_instance_creation() { .containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.ClassInstanceCreation - */ @Test void expression_anonymous_class_instance_creation() { JavaTree.CompilationUnitTreeImpl cu = test("class C { Object m() { return new C() { }; } protected C(){} }"); @@ -403,9 +351,6 @@ void expression_anonymous_class_instance_creation() { .containsOnly(identifier); } - /** - * @see org.eclipse.jdt.core.dom.CreationReference - */ @Test void expression_creation_reference() { JavaTree.CompilationUnitTreeImpl cu = test("class C { C() { } java.util.function.Supplier m() { return C::new; } }"); @@ -424,9 +369,6 @@ void expression_creation_reference() { .containsOnly(keywordNew); } - /** - * @see org.eclipse.jdt.core.dom.MethodReference - */ @Test void expression_method_reference() { JavaTree.CompilationUnitTreeImpl cu = test("class C { java.util.function.Supplier m() { return this::m; } }"); @@ -444,9 +386,6 @@ void expression_method_reference() { .containsOnly(identifier); } - /** - * @see org.eclipse.jdt.core.dom.TypeMethodReference - */ @Test void expression_type_method_reference() { JavaTree.CompilationUnitTreeImpl cu = test("class C { static java.util.function.Supplier m() { return C::m; } }"); @@ -464,9 +403,6 @@ void expression_type_method_reference() { .containsOnly(identifier); } - /** - * @see org.eclipse.jdt.core.dom.SuperMethodReference - */ @Test void expression_super_method_reference() { JavaTree.CompilationUnitTreeImpl cu = test(""" @@ -487,8 +423,8 @@ class S { KeywordSuper keywordSuper = (KeywordSuper) creationReference.expression(); assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); - assertThat(keywordSuper.symbol()) - .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).superSymbol); + assertThat(keywordSuper.symbol().owner()).isSameAs(c.symbol()); + assertThat(keywordSuper.symbol().type()).isSameAs(keywordSuper.symbolType()); IdentifierTreeImpl identifier = (IdentifierTreeImpl) creationReference.method(); assertThat(symbolOf(identifier)) @@ -500,9 +436,6 @@ class S { .containsOnly(identifier); } - /** - * @see org.eclipse.jdt.core.dom.SuperMethodReference - */ @Test void expression_super_method_reference_qualified() { JavaTree.CompilationUnitTreeImpl cu = test(""" @@ -525,8 +458,8 @@ class S { KeywordSuper keywordSuper = (KeywordSuper) mseti.identifier(); assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); - assertThat(keywordSuper.symbol()) - .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).superSymbol); + assertThat(keywordSuper.symbol().owner()).isSameAs(c.symbol()); + assertThat(keywordSuper.symbol().type()).isSameAs(keywordSuper.symbolType()); IdentifierTreeImpl identifier = (IdentifierTreeImpl) creationReference.method(); assertThat(symbolOf(identifier)) @@ -538,10 +471,6 @@ class S { .containsOnly(identifier); } - /** - * @see org.eclipse.jdt.core.dom.SwitchExpression - * @see org.eclipse.jdt.core.dom.YieldStatement - */ @Test void expression_switch() { { @@ -596,14 +525,6 @@ void switch_expression_of_enum_without_default_clause() { assertThat(switchExpression.symbolType().isUnknown()).isTrue(); } - /** - * Pattern Matching for instanceof - * (Preview in Java 14) https://openjdk.java.net/jeps/305 - * (Second Preview in Java 15) https://openjdk.java.net/jeps/375 - * (Final in Java 16) https://openjdk.java.net/jeps/394 - * - * @see org.eclipse.jdt.core.dom.InstanceofExpression - */ @Test void expression_pattern_instanceof() { PatternInstanceOfTree e = (PatternInstanceOfTree) expression("o instanceof String s"); @@ -619,9 +540,6 @@ void expression_pattern_instanceof() { assertThat(i.symbol().declaration()).isEqualTo(v); } - /** - * @see org.eclipse.jdt.core.dom.MethodInvocation - */ @Test void expression_method_invocation() { JavaTree.CompilationUnitTreeImpl cu = test("class C { Object m() { return m(); } }"); @@ -643,9 +561,6 @@ void expression_method_invocation() { .containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.MethodInvocation - */ @Test void expression_method_invocation_recovery() { JavaTree.CompilationUnitTreeImpl cu = test("class C { Object m() { return m(42); } }"); @@ -659,9 +574,6 @@ void expression_method_invocation_recovery() { assertThat(symbolOf(method).usages()).isEmpty(); } - /** - * @see org.eclipse.jdt.core.dom.SuperMethodInvocation - */ @Test void expression_super_method_invocation() { JavaTree.CompilationUnitTreeImpl cu = test("class C extends S { Object m() { return super.m(); } } class S { Object m() { return null; } }"); @@ -681,8 +593,8 @@ void expression_super_method_invocation() { KeywordSuper keywordSuper = (KeywordSuper) e2.expression(); assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); - assertThat(keywordSuper.symbol()) - .isSameAs(((JDTTypeSymbol) typeOf(c).symbol()).superSymbol); + assertThat(keywordSuper.symbol().owner()).isSameAs(c.symbol()); + assertThat(keywordSuper.symbol().type()).isSameAs(keywordSuper.symbolType()); IdentifierTreeImpl i = (IdentifierTreeImpl) e2.identifier(); assertThat(symbolOf(i)) @@ -693,9 +605,6 @@ void expression_super_method_invocation() { .containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.SuperMethodInvocation - */ @Test void expression_super_method_invocation_recovery() { JavaTree.CompilationUnitTreeImpl cu = test("class C extends S { Object m() { return super.m(42); } } class S { Object m() { return null; } }"); @@ -713,9 +622,6 @@ void expression_super_method_invocation_recovery() { assertThat(symbolOf(superClassMethod).usages()).isEmpty(); } - /** - * @see org.eclipse.jdt.core.dom.SuperMethodInvocation - */ @Test void expression_super_method_invocation_qualified() { JavaTree.CompilationUnitTreeImpl cu = test("class T extends S { class C { Object m() { return T.super.m(); } } } class S { Object m() { return null; } }"); @@ -745,8 +651,8 @@ void expression_super_method_invocation_qualified() { KeywordSuper keywordSuper = (KeywordSuper) qualifiedSuper.identifier(); assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass); - assertThat(keywordSuper.symbol()) - .isSameAs(((JDTTypeSymbol) typeOf(t).symbol()).superSymbol); + assertThat(keywordSuper.symbol().owner()).isSameAs(t.symbol()); + assertThat(keywordSuper.symbol().type()).isSameAs(keywordSuper.symbolType()); IdentifierTreeImpl identifier = (IdentifierTreeImpl) qualifiedMethodName.identifier(); assertThat(symbolOf(identifier)) @@ -797,10 +703,10 @@ class A { var lambda = (LambdaExpressionTree) f.initializer(); var p = (VariableTreeImpl) lambda.parameters().get(0); - Symbol newSymbol = ((JDTVariableSymbol) symbolOf(p)); + Symbol newSymbol = p.symbol(); assertThat(newSymbol.declaration().firstToken().range().start().line()).isEqualTo(3); Symbol newOwner = newSymbol.owner(); - assertTrue(newOwner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertThat(newOwner).isSameAs(lambda.symbol()); } /** @@ -828,9 +734,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 = ((JDTVariableSymbol) symbolOf(p)); + Symbol newSymbol = p.symbol(); Symbol newOwner = newSymbol.owner(); - assertTrue(newOwner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertThat(newOwner).isSameAs(lambda.symbol()); } @Test @@ -858,12 +764,9 @@ void m() { LambdaExpressionTreeImpl l2 = (LambdaExpressionTreeImpl) r.expression(); assertThat(typeOf(l2)).isNotSameAs(Type.UNKNOWN); - assertThat(((JDTType) typeOf(l2)).is("java.util.function.Supplier")).isTrue(); + assertThat(typeOf(l2).is("java.util.function.Supplier")).isTrue(); } - /** - * @see org.eclipse.jdt.core.dom.ConditionalExpression - */ @Test void conditional_expression() { String source = """ @@ -884,15 +787,15 @@ void foo(Class[] arr1, Class[] arr2, boolean IdentifierTreeImpl c2 = (IdentifierTreeImpl) cond.falseExpression(); assertThat(typeOf(cond)).isNotSameAs(Type.UNKNOWN); - JDTType tcond = ((JDTType) typeOf(cond)); + Type tcond = typeOf(cond); assertThat(tcond.is("java.lang.Class[]")).isTrue(); assertThat(typeOf(c1)).isNotSameAs(Type.UNKNOWN); - JDTType tc1 = ((JDTType) typeOf(c1)); + Type tc1 = typeOf(c1); assertThat(tc1.is("java.lang.Class[]")).isTrue(); assertThat(typeOf(c2)).isNotSameAs(Type.UNKNOWN); - JDTType tc2 = ((JDTType) typeOf(c2)); + Type tc2 = typeOf(c2); assertThat(tc2.is("java.lang.Class[]")).isTrue(); assertThat(tc2) @@ -900,9 +803,6 @@ void foo(Class[] arr1, Class[] arr2, boolean .isEqualTo(tcond); } - /** - * @see org.eclipse.jdt.core.dom.ConstructorInvocation - */ @Test void statement_constructor_invocation() { JavaTree.CompilationUnitTreeImpl cu = test("class C { C() { this(null); } C(Object p) { } }"); @@ -928,9 +828,6 @@ void statement_constructor_invocation() { .containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.ConstructorInvocation - */ @Test void statement_constructor_invocation_recovery() { JavaTree.CompilationUnitTreeImpl cu = test("class C { C() { this(42); } }"); @@ -947,9 +844,6 @@ void statement_constructor_invocation_recovery() { assertThat(symbolOf(m).usages()).isEmpty(); } - /** - * @see org.eclipse.jdt.core.dom.SuperConstructorInvocation - */ @Test void statement_super_constructor_invocation() { JavaTree.CompilationUnitTreeImpl cu = test("class C extends S { C() { super(); } } class S { S() { } }"); @@ -976,9 +870,6 @@ void statement_super_constructor_invocation() { .containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.SuperConstructorInvocation - */ @Test void statement_super_constructor_invocation_recovery() { JavaTree.CompilationUnitTreeImpl cu = test("class C extends S { C() { super(42); } } class S { S() { } }"); @@ -1026,9 +917,6 @@ void statement_for() { assertThat(symbolOf(v).declaration()).isSameAs(v); } - /** - * @see org.eclipse.jdt.core.dom.ImportDeclaration - */ @Test void declaration_import() { JavaTree.CompilationUnitTreeImpl cu = test("import java.util.List;"); @@ -1060,14 +948,6 @@ void declaration_type() { assertThat(typeParameter.symbol().isUnknown()).isTrue(); } - /** - * Records - * (Preview in Java 14) https://openjdk.java.net/jeps/359 - * (Second Preview in Java 15) https://openjdk.java.net/jeps/384 - * (Final in Java 16) https://openjdk.java.net/jeps/395 - * - * @see org.eclipse.jdt.core.dom.RecordDeclaration - */ @Test void declaration_record() { JavaTree.CompilationUnitTreeImpl cu = test("record R(int component1, int component2) implements java.io.Serializable { public R { } }"); @@ -1087,14 +967,6 @@ void declaration_record() { assertThat(m.closeParenToken()).isNull(); } - /** - * Sealed Classes - * (Preview in Java 15) https://openjdk.java.net/jeps/360 - * (Second Preview in Java 16) https://openjdk.java.net/jeps/397 - * (Final in Java 17) https://openjdk.java.net/jeps/409 - * - * @see org.eclipse.jdt.core.dom.TypeDeclaration - */ @Test void declaration_sealed_class() { JavaTree.CompilationUnitTreeImpl cu = test("sealed class Shape permits Circle, Package.Rectangle { }"); @@ -1124,10 +996,6 @@ void declaration_sealed_class() { assertThat(c.modifiers().get(0).firstToken()).is("non-sealed"); } - /** - * {@link org.eclipse.jdt.core.dom.ITypeBinding#getTypeDeclaration()} should be used - * for {@link JDTSymbol#usages()} and {@link JDTSymbol#declaration()} - */ @Test void declaration_type_parameterized() { JavaTree.CompilationUnitTreeImpl cu = test("class C { C field; }"); @@ -1138,17 +1006,12 @@ void declaration_type_parameterized() { assertThat(typeOf(c).symbol().usages()) .containsOnly(i); assertThat(typeOf(fieldType)).isNotSameAs(Type.UNKNOWN); - assertThat(((JDTTypeSymbol) typeOf(fieldType).symbol()).declaration()) - .isSameAs(fieldType.symbolType().symbol().declaration()) + assertThat(fieldType.symbolType().symbol().declaration()) .isSameAs(c); - assertThat(((JDTTypeSymbol) typeOf(fieldType).symbol()).usages()) - .containsExactlyElementsOf(fieldType.symbolType().symbol().usages()) + assertThat(fieldType.symbolType().symbol().usages()) .containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.EnumConstantDeclaration - */ @Test void declaration_enum_constant() { JavaTree.CompilationUnitTreeImpl cu = test("enum E { C ; E() { } }"); @@ -1169,9 +1032,6 @@ void declaration_enum_constant() { .containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.EnumConstantDeclaration - */ @Test void declaration_enum_constant_recovered_constructor() { JavaTree.CompilationUnitTreeImpl cu = test("@GenerateConstructor enum E { A(0), B(1) ; @GenerateGetter Integer value; }"); @@ -1183,9 +1043,6 @@ void declaration_enum_constant_recovered_constructor() { assertThat(i.symbol().isUnknown()).isTrue(); } - /** - * @see org.eclipse.jdt.core.dom.EnumConstantDeclaration - */ @Test void declaration_enum_constant_anonymous() { JavaTree.CompilationUnitTreeImpl cu = test("enum E { C { }; E(){} }"); @@ -1223,10 +1080,6 @@ void declaration_method() { assertThat(symbolOf(m).declaration()).isSameAs(m); } - /** - * {@link org.eclipse.jdt.core.dom.IMethodBinding#getMethodDeclaration()} should be used - * for {@link JDTSymbol#usages()} and {@link JDTSymbol#declaration()} - */ @Test void declaration_method_parameterized() { JavaTree.CompilationUnitTreeImpl cu = test("class C { void m(T t) { m(42); } }"); @@ -1240,11 +1093,9 @@ void declaration_method_parameterized() { .containsOnly(i); assertThat(symbolOf(methodInvocation)) .isNotNull(); - assertThat(((JDTMethodSymbol) symbolOf(methodInvocation)).declaration()) - .isSameAs(methodInvocation.methodSymbol().declaration()) + assertThat(methodInvocation.methodSymbol().declaration()) .isSameAs(m); - assertThat(((JDTMethodSymbol) symbolOf(methodInvocation)).usages()) - .containsExactlyElementsOf(methodInvocation.methodSymbol().usages()) + assertThat(methodInvocation.methodSymbol().usages()) .containsOnly(i); } @@ -1447,7 +1298,7 @@ void type_try_with_resource() { VariableTreeImpl v = (VariableTreeImpl) s.resourceList().get(0); AbstractTypedTree t = (AbstractTypedTree) v.type(); assertThat(typeOf(t)).isNotSameAs(Type.UNKNOWN); - JDTType t2 = (JDTType) v.symbol().type(); + Type t2 = v.symbol().type(); assertThat(t2).isNotNull(); assertThat(t2.isUnknown()).isTrue(); assertThat(t2).isNotSameAs(Type.UNKNOWN); @@ -1575,9 +1426,8 @@ void symbol_unknown() { .isFalse(); assertThat(i.symbolType().isIntersectionType()).isFalse(); - assertThat(i.symbolType().getIntersectionTypes()) - .extracting(Type::fullyQualifiedName) - .containsExactly("Recovered#typeBindingLUnknownInterface;0"); + assertThat(i.symbolType().isUnknown()).isTrue(); + assertThat(i.symbolType().getIntersectionTypes()).containsExactly(i.symbolType()); } @Test @@ -1587,11 +1437,9 @@ 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(((JDTVariableSymbol) symbolOf(parameter)).metadata().annotations().size()) - .isEqualTo(parameter.symbol().metadata().annotations().size()) + assertThat(parameter.symbol().metadata().annotations().size()) .isEqualTo(1); - assertThat(JUtils.parameterAnnotations(((JDTMethodSymbol) symbolOf(m)), 0).annotations().size()) - .isEqualTo(JUtils.parameterAnnotations(m.symbol(), 0).annotations().size()) + assertThat(m.symbol().declarationParameters().get(0).metadata().annotations().size()) .isEqualTo(1); } @@ -1608,15 +1456,9 @@ void annotation_on_var_type_local_variable() { assertThat(annotations.get(0).symbol().name()).isEqualTo("Annotation"); } - /** - * @see org.eclipse.jdt.core.dom.LabeledStatement - */ @Nested class Labels { - /** - * @see org.eclipse.jdt.core.dom.BreakStatement - */ - @Test + @Test void statement_break() { CompilationUnitTree cu = test("class C { void m() { i: break i; } }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); @@ -1645,10 +1487,7 @@ void statement_break() { .containsOnly(i); } - /** - * @see org.eclipse.jdt.core.dom.ContinueStatement - */ - @Test + @Test void statement_continue() { CompilationUnitTree cu = test("class C { void m() { i: for(;;) continue i; } }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); @@ -1750,13 +1589,6 @@ private static JavaTree.CompilationUnitTreeImpl test(String source) { @Test void not_need_to_skip_implicit_break_statement() { final String source = "class C { void m() { switch (0) { case 0 -> { } } } }"; - CompilationUnit cu = createAST(source); - TypeDeclaration c = (TypeDeclaration) cu.types().get(0); - MethodDeclaration m = c.getMethods()[0]; - SwitchStatement s = (SwitchStatement) m.getBody().statements().get(0); - Block block = (Block) s.statements().get(1); - assertThat(block.statements()).isEmpty(); - CompilationUnitTree compilationUnit = test(source); ClassTree cls = (ClassTree) compilationUnit.types().get(0); MethodTree method = (MethodTree) cls.members().get(0); @@ -1765,26 +1597,6 @@ void not_need_to_skip_implicit_break_statement() { assertThat(blockStatement.body()).isEmpty(); } - private CompilationUnit createAST(String source) { - JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - Map options = new HashMap<>(JavaCore.getOptions()); - JavaCore.setComplianceOptions(version.effectiveJavaVersionAsString(), options); - options.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled"); - astParser.setCompilerOptions(options); - astParser.setEnvironment( - new String[]{}, - new String[]{}, - new String[]{}, - true - ); - astParser.setUnitName("File.java"); - astParser.setResolveBindings(true); - astParser.setBindingsRecovery(true); - astParser.setSource(source.toCharArray()); - return (CompilationUnit) astParser.createAST(null); - } - @Test void super_constructor_external_inner_class() { final String source = """ @@ -1912,9 +1724,9 @@ int consume(C c) { assertNotEquals(symbol1, p2.symbol()); Symbol owner1 = symbol1.owner(); - assertTrue(owner1 instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertThat(owner1).isSameAs(lambda2.symbol()); Symbol owner2 = symbol2.owner(); - assertTrue(owner2 instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertThat(owner2).isSameAs(lambdaA.symbol()); assertNotEquals(owner1, owner2); } @@ -1939,7 +1751,7 @@ class C { Symbol jOwner = j.symbol().owner(); Symbol kOwner = k.symbol().owner(); assertNotEquals(jOwner, kOwner); - assertTrue(iOwner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertThat(iOwner).isSameAs(lambda.symbol()); } @Test @@ -1982,14 +1794,16 @@ interface F extends java.util.function.Consumer {} """); ClassTree classTree = (ClassTree) cu.types().get(0); var k = (VariableTree) classTree.members().get(0); - Symbol s1 = ((LambdaExpressionTree) k.initializer()).parameters().get(0).symbol(); + var lambda1 = (LambdaExpressionTree) k.initializer(); + Symbol s1 = lambda1.parameters().get(0).symbol(); Symbol s1Owner = s1.owner(); - assertThat(s1Owner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); + assertThat(s1Owner).isSameAs(lambda1.symbol()); var j = (VariableTree) classTree.members().get(1); - Symbol s2 = ((LambdaExpressionTree) j.initializer()).parameters().get(0).symbol(); + var lambda2 = (LambdaExpressionTree) j.initializer(); + Symbol s2 = lambda2.parameters().get(0).symbol(); Symbol s2Owner = s2.owner(); - assertThat(s2Owner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); + assertThat(s2Owner).isSameAs(lambda2.symbol()); assertNotEquals(s1Owner, s2Owner); // For sanity, check that the symbols are not equal @@ -2031,9 +1845,9 @@ void f2() { Symbol ownerC = pOfC.symbol().owner(); Symbol ownerB = pOfB.symbol().owner(); - assertThat(ownerA instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); - assertThat(ownerB instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); - assertThat(ownerC instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()).isTrue(); + assertThat(ownerA).isSameAs(lambdaA.symbol()); + assertThat(ownerB).isSameAs(lambdaB.symbol()); + assertThat(ownerC).isSameAs(lambdaC.symbol()); assertNotEquals(ownerA, ownerC); assertNotEquals(ownerA, ownerB); assertNotEquals(ownerC, ownerB); diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JParserTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java similarity index 76% rename from java-frontend/src/test/java/org/sonar/java/jdt/JParserTest.java rename to java-frontend/src/test/java/org/sonar/java/model/JParserTest.java index 533a83fd62f..239186e7882 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JParserTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/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.jdt; +package org.sonar.java.model; import com.sonar.sslr.api.RecognitionException; import java.io.BufferedInputStream; @@ -27,11 +27,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Deque; -import java.util.LinkedList; import java.util.List; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; import java.util.jar.Attributes; @@ -39,14 +36,6 @@ import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import org.apache.commons.io.FileUtils; -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.internal.compiler.parser.TerminalToken; -import org.eclipse.jdt.internal.formatter.Token; -import org.eclipse.jdt.internal.formatter.TokenManager; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; @@ -57,12 +46,7 @@ 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; @@ -84,14 +68,12 @@ import org.sonar.plugins.java.api.tree.RecordPatternTree; import org.sonar.plugins.java.api.tree.ReturnStatementTree; import org.sonar.plugins.java.api.tree.SwitchExpressionTree; -import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TryStatementTree; import org.sonar.plugins.java.api.tree.TypePatternTree; import org.sonar.plugins.java.api.tree.VariableTree; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -104,8 +86,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -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; @@ -136,8 +116,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 = ((JDTParserConfig) FILE_BY_FILE.create(JavaVersionImpl.fromString(version), classpath)).jdtParser(); - assertThrows(RecognitionException.class, () -> JDTParser.parse(astParser, version, "A", "class A { }")); + JParserConfig config = FILE_BY_FILE.create(JavaVersionImpl.fromString(version), classpath); + assertThrows(RecognitionException.class, () -> config.parse("A", "class A { }")); } @Test @@ -509,86 +489,6 @@ record Box(int value) {} assertThat(variableTree.simpleName().isUnnamedVariable()).isTrue(); } - @Test - void test_first_index_of_tokens_in_eclipse_ast() { - String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); - String unitName = "C.java"; - String source = "class C { }"; - - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - JavaCore.setComplianceOptions(version, JavaCore.getOptions()); - astParser.setResolveBindings(true); - astParser.setUnitName(unitName); - astParser.setSource(source.toCharArray()); - CompilationUnit compilationUnit = (CompilationUnit) astParser.createAST(null); - TokenManager tokenManager = JDTParser.createTokenManager(version, unitName, source); - - 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"); - } - - @Test - void test_comment_tokens() { - String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); - String unitName = "C.java"; - String source = """ - class A { - // line comment - /* block comment */ - /// markdown comment 1 - /// markdown comment 2 - /** - * javadoc comment - */ - void foo() {} - } - """; - TokenManager tokens = JDTParser.createTokenManager(version, unitName, source); - - assertThat(tokens.size()).isEqualTo(15); - - Token token0 = tokens.get(0); - assertThat(token0.toString(source)).isEqualTo("class"); - assertThat(token0.isComment()).isFalse(); - assertThat(isComment(token0)).isFalse(); - assertThatThrownBy(() -> convertTokenTypeToCommentKind(token0)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unexpected value: TokenNameclass"); - - Token token3 = tokens.get(3); - assertThat(token3.toString(source)).isEqualTo("// line comment"); - assertThat(token3.isComment()).isTrue(); - assertThat(isComment(token3)).isTrue(); - assertThat(convertTokenTypeToCommentKind(token3)).isEqualTo(CommentKind.LINE); - - Token token4 = tokens.get(4); - assertThat(token4.toString(source)).isEqualTo("/* block comment */"); - assertThat(token4.isComment()).isTrue(); - assertThat(isComment(token4)).isTrue(); - assertThat(convertTokenTypeToCommentKind(token4)).isEqualTo(CommentKind.BLOCK); - - Token token5 = tokens.get(5); - assertThat(token5.toString(source)).isEqualTo("/// markdown comment 1\n /// markdown comment 2"); - assertThat(token5.isComment()).isTrue(); - assertThat(isComment(token5)).isTrue(); - assertThat(convertTokenTypeToCommentKind(token5)).isEqualTo(CommentKind.MARKDOWN); - - Token token6 = tokens.get(6); - assertThat(token6.toString(source)).isEqualTo("/**\n * javadoc comment\n */"); - assertThat(token6.isComment()).isTrue(); - assertThat(isComment(token6)).isTrue(); - assertThat(convertTokenTypeToCommentKind(token6)).isEqualTo(CommentKind.JAVADOC); - - Token token7 = tokens.get(7); - assertThat(token7.toString(source)).isEqualTo("void"); - assertThat(token7.isComment()).isFalse(); - assertThat(isComment(token7)).isFalse(); - } - @Test void dont_include_running_VM_Bootclasspath_if_jvm_rt_jar_already_provided_in_classpath(@TempDir Path tempFolder) throws IOException { VariableTree s1 = parseAndGetVariable("class C { void m() { String a; } }"); @@ -597,7 +497,7 @@ void dont_include_running_VM_Bootclasspath_if_jvm_rt_jar_already_provided_in_cla Path fakeRt = tempFolder.resolve("rt.jar"); Files.createFile(fakeRt); s1 = parseAndGetVariable("class C { void m() { String a; } }", fakeRt.toFile()); - assertThat(s1.type().symbolType().fullyQualifiedName()).isEqualTo("Recovered#typeBindingLString;0"); + assertThat(s1.type().symbolType().isUnknown()).isTrue(); } @ParameterizedTest @@ -613,7 +513,7 @@ void dont_include_running_VM_Bootclasspath_if_android_runtime_already_provided_i Path fakeAndroidSdk = Path.of("src", "test", "resources", "android.jar").toRealPath(); a = parseAndGetVariable(source, androidVersion, fakeAndroidSdk.toFile()); b = (VariableTree) ((BlockTree) a.parent()).body().get(1); - assertThat(a.type().symbolType().fullyQualifiedName()).isEqualTo("Recovered#typeBindingLString;0"); + assertThat(a.type().symbolType().isUnknown()).isTrue(); assertThat(b.type().symbolType().fullyQualifiedName()).isEqualTo("java.lang.Integer"); } @@ -670,90 +570,6 @@ void test_parse_as_batch() throws Exception { assertResultsOfParsing(results, inputFilesProcessed); } - @Test - void failing_batch_mode_should_continue_file_by_file() { - List inputFiles = Arrays.asList( - TestUtils.inputFile("src/test/files/metrics/Classes.java"), - TestUtils.inputFile("src/test/files/metrics/Methods.java")); - List trace = new ArrayList<>(); - BiConsumer action = (inputFile, result) -> { - trace.addAll(logTester.logs()); - logTester.clear(); - try { - JavaTree.CompilationUnitTreeImpl tree = result.get(); - trace.add("[action] analyse class " + ((ClassTree)tree.types().get(0)).simpleName().name() + " in " + inputFile.filename()); - } catch (Exception e) { - trace.add("[action] handle " + e.getClass().getSimpleName() + ": " + e.getMessage()); - } - }; - BatchWithException batchWithException = new BatchWithException(); - // exception before the first file - batchWithException.exceptions.push(new NullPointerException("Boom!")); - batchWithException.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), action); - trace.addAll(logTester.logs()); - logTester.clear(); - assertThat(trace).containsExactly( - "Unexpected NullPointerException: Boom!", - "Fallback to file by file analysis for 2 files", - "[action] analyse class HelloWorld in Classes.java", - "[action] analyse class MyClass in Methods.java"); - } - - @Test - void successful_batch_mode_with_missing_analyzed_files_should_continue_file_by_file() { - List inputFiles = Arrays.asList( - TestUtils.inputFile("src/test/files/metrics/Classes.java"), - TestUtils.inputFile("src/test/files/metrics/Methods.java")); - 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.jdtParser()).thenReturn(astParser); - - config.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), doNothingAction); - - assertThat(logTester.logs()).containsExactly( - "Unexpected AnalysisException: 2/2 files were not analyzed by the batch mode", - "Fallback to file by file analysis for 2 files"); - logTester.clear(); - } - - @Test - void failing_batch_mode_should_stop_file_by_file_when_cancelled() { - List inputFiles = Arrays.asList( - TestUtils.inputFile("src/test/files/metrics/Classes.java"), - TestUtils.inputFile("src/test/files/metrics/Methods.java")); - List trace = new ArrayList<>(); - AtomicBoolean isCanceled = new AtomicBoolean(false); - BiConsumer action = (inputFile, result) -> { - trace.addAll(logTester.logs()); - logTester.clear(); - try { - JavaTree.CompilationUnitTreeImpl tree = result.get(); - trace.add("[action] analyze class " + ((ClassTree)tree.types().get(0)).simpleName().name() + " in " + inputFile.filename()); - // cancel analysis after first file - isCanceled.set(true); - } catch (Exception e) { - trace.add("[action] handle " + e.getClass().getSimpleName() + ": " + e.getMessage()); - } - }; - - JParserConfig config = BATCH - .create(MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), false); - AnalysisProgress analysisProgress = new AnalysisProgress(inputFiles.size()); - assertThatThrownBy(() -> config.parse(inputFiles, isCanceled::get, analysisProgress, action)) - .isInstanceOf(OperationCanceledException.class); - - trace.addAll(logTester.logs()); - logTester.clear(); - assertThat(trace).containsExactly( - "Starting batch processing.", - "[action] analyze class HelloWorld in Classes.java", - "Batch processing: Cancelled!"); - // Missing: [action] analyse class MyClass in Methods.java - } - @Test void test_no_line_continuation_in_text_blocks() { CompilationUnitTree tree = parse(new File("src/test/files/metrics/TextBlock.java")); @@ -809,39 +625,6 @@ public static List tokens(JavaTree tree) { return tokens; } - @Test - 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 "); - BatchWithException batchWithException = new BatchWithException(); - // exception before the first file - batchWithException.exceptions.push(new NullPointerException("Boom!")); - batchWithException.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), action); - trace.addAll(logTester.logs()); - logTester.clear(); - assertThat(trace).containsExactly( - "Unexpected NullPointerException: Boom!"); - } - - static class BatchWithException extends JDTParserConfig.Batch { - - Deque exceptions = new LinkedList<>(); - - public BatchWithException() { - super(MAXIMUM_SUPPORTED_JAVA_VERSION, DEFAULT_CLASSPATH, false); - } - - @Override - public ASTParser jdtParser() { - if (!exceptions.isEmpty()) { - throw exceptions.pop(); - } - return super.jdtParser(); - } - - } - private void assertResultsOfParsing(List results, List inputFilesProcessed) throws Exception { assertThat(inputFilesProcessed).hasSize(2); assertThat(inputFilesProcessed.get(0).filename()).isEqualTo("Classes.java"); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java new file mode 100644 index 00000000000..89dc734b114 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java @@ -0,0 +1,71 @@ +/* + * 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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.semantic.Sema; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class JSemaTest { + + @Test + void testGetClassTypeUnknown() { + assertThat(sema.getClassType("com.google.common.collect.ImmutableMap")). + isEqualTo(Type.UNKNOWN); + assertThat(sema.getClassType("java.lang.Object")) + .isNotEqualTo(Type.UNKNOWN); + } + + @Test + void resolvePackageAnnotations() { + assertThat(sema.getClassType("org.sonar.java.resolve.targets.ClassWithConstants").symbol().owner().metadata().annotations()) + .hasSize(1); + } + + @Test + void getClassType() { + assertAll( + () -> + assertThat(sema.getClassType("Nonexistent")) + .isSameAs(Type.UNKNOWN), + () -> + assertThat(sema.getClassType("void")) + .isNotEqualTo(Type.UNKNOWN), + () -> + assertThat(sema.getClassType("java.util.Map")) + .isNotEqualTo(Type.UNKNOWN), + () -> + assertThat(sema.getClassType("java.util.Map$Entry")) + .isNotEqualTo(Type.UNKNOWN), + () -> + assertThat(sema.getClassType("java.util.Map$Entry[][]")) + .isNotEqualTo(Type.UNKNOWN) + ); + } + + private Sema sema; + + @BeforeEach + void setup() { + sema = JParserTestUtils.sema(JParserTestUtils.parse("")); + } + +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JSymbolMetadataTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java similarity index 97% rename from java-frontend/src/test/java/org/sonar/java/jdt/JSymbolMetadataTest.java rename to java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java index d41a8a2f23d..a499dd4fb8c 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JSymbolMetadataTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/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.jdt; +package org.sonar.java.model; import java.io.IOException; import java.nio.file.Path; @@ -26,17 +26,14 @@ 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; import org.sonar.java.model.statement.ExpressionStatementTreeImpl; 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.NullabilityType; +import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; @@ -72,10 +69,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[]) ((JDTTypeSymbol) typeOf(c).symbol()).metadata().annotations().get(0).values().get(0).value(); + Object[] value = (Object[]) typeOf(c).symbol().metadata().annotations().get(0).values().get(0).value(); assertThat(value).hasSize(2); - assertThat(((JDTVariableSymbol) value[0]).isEnum()).isTrue(); - assertThat(((JDTVariableSymbol) value[1]).isEnum()).isTrue(); + assertThat(((Symbol.VariableSymbol) value[0]).isEnum()).isTrue(); + assertThat(((Symbol.VariableSymbol) value[1]).isEnum()).isTrue(); } @Test diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java similarity index 78% rename from java-frontend/src/test/java/org/sonar/java/jdt/JSymbolTest.java rename to java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java index d6e922af002..4ce55fbb152 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java @@ -14,19 +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.jdt; +package org.sonar.java.model; -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.IBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; 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,12 +43,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -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.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.typeOf; import static org.sonar.java.model.assertions.SymbolAssert.assertThat; @@ -69,14 +55,11 @@ void name() { MethodTreeImpl constructor = (MethodTreeImpl) c.members().get(0); MethodTreeImpl method = (MethodTreeImpl) c.members().get(1); - assertThat(((JDTTypeSymbol) typeOf(c).symbol())) - .hasSameNameAs(c.symbol()) + assertThat(typeOf(c).symbol()) .hasName("C"); - assertThat(((JDTMethodSymbol) symbolOf(constructor))) - .hasSameNameAs(constructor.symbol()) + assertThat(constructor.symbol()) .hasName(""); - assertThat(((JDTMethodSymbol) symbolOf(method))) - .hasSameNameAs(method.symbol()) + assertThat(method.symbol()) .hasName("m"); } @@ -91,39 +74,36 @@ void owner() { ClassTreeImpl c3 = (ClassTreeImpl) m.block().body().get(0); VariableTreeImpl u = (VariableTreeImpl) c3.members().get(0); - assertThat(((JDTTypeSymbol) typeOf(c1).symbol())) - .as("of top-level class") - .hasOwner(sema(cu).packageSymbol(typeBinding(c1).getPackage())); + Symbol packageSymbol = c1.symbol().owner(); + assertThat(packageSymbol.isPackageSymbol()).isTrue(); + assertThat(packageSymbol.name()).isEmpty(); - assertThat(((JDTTypeSymbol) typeOf(c2).symbol())) + assertThat(typeOf(c2).symbol()) .as("of nested class") - .hasOwner(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasOwner(typeOf(c1).symbol()); - assertThat(((JDTTypeSymbol) typeOf(c3).symbol())) + assertThat(typeOf(c3).symbol()) .as("of local class") - .hasOwner(((JDTMethodSymbol) symbolOf(m))); + .hasOwner(m.symbol()); - assertThat(((JDTMethodSymbol) symbolOf(m))) + assertThat(m.symbol()) .as("of method") - .hasOwner(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasOwner(typeOf(c1).symbol()); - assertThat(((JDTVariableSymbol) symbolOf(f))) + assertThat(f.symbol()) .as("of field") - .hasOwner(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasOwner(typeOf(c1).symbol()); - assertThat(((JDTVariableSymbol) symbolOf(p))) + assertThat(p.symbol()) .as("of method parameter") - .hasOwner(((JDTMethodSymbol) symbolOf(m))); + .hasOwner(m.symbol()); - assertThat(((JDTTypeSymbol) symbolOf(p).type().symbol())) + assertThat(p.symbol().type().symbol()) .as("of type int") .hasOwner(Symbol.ROOT_PACKAGE) .hasSameHashCodeAs(p.type().symbolType().symbol().hashCode()); - assertThat(sema(cu).packageSymbol(null)) - .isEqualTo(Symbol.ROOT_PACKAGE); - - JDTType uType = ((JDTType) symbolOf(u).type()); + Type uType = u.symbol().type(); Symbol.TypeSymbol uTypeSymbol = uType.symbol(); assertThat(uType.isUnknown()).isTrue(); assertThat(uTypeSymbol.isUnknown()).isTrue(); @@ -168,13 +148,13 @@ void owner_local_record() { ClassTreeImpl r = (ClassTreeImpl) m.block().body().get(0); VariableTreeImpl p = (VariableTreeImpl) r.recordComponents().get(0); - assertThat(((JDTVariableSymbol) symbolOf(p))) + assertThat(p.symbol()) .as("of record component") - .hasOwner(((JDTTypeSymbol) typeOf(r).symbol())); + .hasOwner(typeOf(r).symbol()); - assertThat(((JDTMethodSymbol) symbolOf(m))) + assertThat(m.symbol()) .as("of method") - .hasOwner(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasOwner(typeOf(c1).symbol()); } @Test @@ -189,7 +169,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 JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertThat(owner).isSameAs(lambda.symbol()); } @Test @@ -208,7 +188,7 @@ void foo() { LambdaExpressionTree lambda = (LambdaExpressionTree) bindingInsideMethod.initializer(); VariableTree l = lambda.parameters().get(0); Symbol owner = l.symbol().owner(); - assertTrue(owner instanceof JDTMethodSymbol methodSymbol && methodSymbol.isLambda()); + assertThat(owner).isSameAs(lambda.symbol()); } @Test @@ -217,8 +197,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(((JDTTypeSymbol) symbolOf(uField).type().symbol())) - .hasSameHashCodeAs(((JDTTypeSymbol) symbolOf(vField).type().symbol())); + assertThat(uField.symbol().type().symbol()) + .hasSameHashCodeAs(vField.symbol().type().symbol()); } @Test @@ -231,29 +211,29 @@ void enclosingClass() { VariableTreeImpl p = (VariableTreeImpl) m.parameters().get(0); ClassTreeImpl c3 = (ClassTreeImpl) m.block().body().get(0); - assertThat(((JDTTypeSymbol) typeOf(c1).symbol())) + assertThat(typeOf(c1).symbol()) .as("of top-level class") - .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(typeOf(c1).symbol()); - assertThat(((JDTTypeSymbol) typeOf(c2).symbol())) + assertThat(typeOf(c2).symbol()) .as("of nested class") - .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(typeOf(c1).symbol()); - assertThat(((JDTTypeSymbol) typeOf(c3).symbol())) + assertThat(typeOf(c3).symbol()) .as("of local class") - .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(typeOf(c1).symbol()); - assertThat(((JDTMethodSymbol) symbolOf(m))) + assertThat(m.symbol()) .as("of method") - .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(typeOf(c1).symbol()); - assertThat(((JDTVariableSymbol) symbolOf(f))) + assertThat(f.symbol()) .as("of field") - .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(typeOf(c1).symbol()); - assertThat(((JDTVariableSymbol) symbolOf(p))) + assertThat(p.symbol()) .as("of method parameter") - .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(typeOf(c1).symbol()); } @Test @@ -264,13 +244,13 @@ void record_enclosingClass() { ClassTreeImpl c2 = (ClassTreeImpl) c1.members().get(0); VariableTreeImpl p = (VariableTreeImpl) c2.recordComponents().get(0); - assertThat(((JDTVariableSymbol) symbolOf(f))) + assertThat(f.symbol()) .as("of field") - .hasEnclosingClass(((JDTTypeSymbol) typeOf(c1).symbol())); + .hasEnclosingClass(typeOf(c1).symbol()); - assertThat(((JDTVariableSymbol) symbolOf(p))) + assertThat(p.symbol()) .as("of method parameter") - .hasEnclosingClass(((JDTTypeSymbol) typeOf(c2).symbol())); + .hasEnclosingClass(typeOf(c2).symbol()); } @Test @@ -297,17 +277,11 @@ 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 = ((JDTTypeSymbol) typeOf(c).symbol()); + Symbol.TypeSymbol t = typeOf(c).symbol(); - JDTVariableSymbol variableSymbol = ((JDTVariableSymbol) symbolOf(v)); + Symbol variableSymbol = v.symbol(); assertThat(variableSymbol).isSameAs(v.symbol()); - JDTInitializerBlockSymbol initializerBlock = (JDTInitializerBlockSymbol) variableSymbol.owner(); - - if (isStatic) { - assertThat(initializerBlock).isSameAs(sema(cu).staticInitializerBlockSymbol((JDTTypeSymbol) c.symbol())); - } else { - assertThat(initializerBlock).isSameAs(sema(cu).initializerBlockSymbol((JDTTypeSymbol) c.symbol())); - } + Symbol.MethodSymbol initializerBlock = (Symbol.MethodSymbol) variableSymbol.owner(); assertThat(variableSymbol).hasEnclosingClass(t); assertThat(initializerBlock).hasEnclosingClass(t).hasOwner(t); @@ -364,17 +338,17 @@ void type() { VariableTreeImpl field = (VariableTreeImpl) c.members().get(0); MethodTreeImpl method = (MethodTreeImpl) c.members().get(1); - assertThat(((JDTTypeSymbol) typeOf(c).symbol()).owner()) + assertThat(typeOf(c).symbol().owner()) .isOfSameTypeAs(c.symbol().owner()) .isOfUnknownType(); - assertThat(((JDTTypeSymbol) typeOf(c).symbol())) - .isOfType(((JDTType) typeOf(c))); + assertThat(typeOf(c).symbol()) + .isOfType(typeOf(c)); - assertThat(((JDTVariableSymbol) symbolOf(field))) - .isOfType(((JDTType) symbolOf(field).type())); + assertThat(field.symbol()) + .isOfType(field.symbol().type()); - assertThat(((JDTMethodSymbol) symbolOf(method))).isOfUnknownType(); + assertThat(method.symbol()).isOfUnknownType(); } @Test @@ -404,8 +378,8 @@ void var_type_as_local_variable() { assertThat(variable.symbol()) .isOfType("java.util.ArrayList"); - assertThat(((JDTVariableSymbol) symbolOf(variable))) - .isOfType(((JDTType) symbolOf(variable).type())); + assertThat(variable.symbol()) + .isOfType(variable.symbol().type()); } @Test @@ -437,18 +411,6 @@ void test_with_type_annotation() { assertThat(parameterMetadata.annotations()).hasSize(1); } - @Test - 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); - JDTSema jSema = new JDTSema(cu.getAST()); - IVariableBinding mock = mock(IVariableBinding.class); - when(mock.getKind()).thenReturn(IBinding.VARIABLE); - JDTVariableSymbol jVariableSymbol = new JDTVariableSymbol(jSema, mock); - assertThat(jVariableSymbol.metadata()).isNotNull(); - } - @Nested class kinds { @@ -467,30 +429,30 @@ void parse() { @Test void isPackageSymbol() { - JDTTypeSymbol c1Symbol = ((JDTTypeSymbol) typeOf(c1).symbol()); + Symbol.TypeSymbol c1Symbol = typeOf(c1).symbol(); assertThat(c1Symbol.isPackageSymbol()).isFalse(); assertThat(c1Symbol.owner().isPackageSymbol()).isTrue(); } @Test void isTypeSymbol() { - assertThat(((JDTTypeSymbol) typeOf(c1).symbol()).isTypeSymbol()).isTrue(); - assertThat(((JDTMethodSymbol) symbolOf(foo)).isTypeSymbol()).isFalse(); - assertThat(((JDTVariableSymbol) symbolOf(f)).isTypeSymbol()).isFalse(); + assertThat(typeOf(c1).symbol().isTypeSymbol()).isTrue(); + assertThat(foo.symbol().isTypeSymbol()).isFalse(); + assertThat(f.symbol().isTypeSymbol()).isFalse(); } @Test void isMethodSymbol() { - assertThat(((JDTTypeSymbol) typeOf(c1).symbol()).isMethodSymbol()).isFalse(); - assertThat(((JDTMethodSymbol) symbolOf(foo)).isMethodSymbol()).isTrue(); - assertThat(((JDTVariableSymbol) symbolOf(f)).isMethodSymbol()).isFalse(); + assertThat(typeOf(c1).symbol().isMethodSymbol()).isFalse(); + assertThat(foo.symbol().isMethodSymbol()).isTrue(); + assertThat(f.symbol().isMethodSymbol()).isFalse(); } @Test void isVariableSymbol() { - assertThat(((JDTTypeSymbol) typeOf(c1).symbol()).isVariableSymbol()).isFalse(); - assertThat(((JDTMethodSymbol) symbolOf(foo)).isVariableSymbol()).isFalse(); - assertThat(((JDTVariableSymbol) symbolOf(f)).isVariableSymbol()).isTrue(); + assertThat(typeOf(c1).symbol().isVariableSymbol()).isFalse(); + assertThat(foo.symbol().isVariableSymbol()).isFalse(); + assertThat(f.symbol().isVariableSymbol()).isTrue(); } } @@ -592,10 +554,10 @@ void isVolatile() { + " int b;" + "}"); - JDTVariableSymbol a = variable(cu, 0); + Symbol a = variable(cu, 0); assertThat(a.isVolatile()).isTrue(); - JDTVariableSymbol b = variable(cu, 1); + Symbol b = variable(cu, 1); assertThat(b.isVolatile()).isFalse(); } @@ -605,8 +567,8 @@ void isInterface() { "interface I1 { } " + "class C1 { }"); - assertThat(((JDTTypeSymbol) typeOf(((ClassTreeImpl) cu.types().get(0))).symbol()).isInterface()).isTrue(); - assertThat(((JDTTypeSymbol) typeOf(((ClassTreeImpl) cu.types().get(1))).symbol()).isInterface()).isFalse(); + assertThat(typeOf(((ClassTreeImpl) cu.types().get(0))).symbol().isInterface()).isTrue(); + assertThat(typeOf(((ClassTreeImpl) cu.types().get(1))).symbol().isInterface()).isFalse(); } @Test @@ -614,39 +576,39 @@ void isEnum() { JavaTree.CompilationUnitTreeImpl cu = test("class C1 { int a; void foo() { }} enum E1 { ACONST; }"); ClassTreeImpl c1 = (ClassTreeImpl) cu.types().get(0); - JDTTypeSymbol c1Symbol = ((JDTTypeSymbol) typeOf(c1).symbol()); + Symbol.TypeSymbol c1Symbol = typeOf(c1).symbol(); assertThat(c1Symbol.isEnum()).isFalse(); - JDTVariableSymbol a = ((JDTVariableSymbol) symbolOf(((VariableTreeImpl) c1.members().get(0)))); + Symbol a = ((VariableTreeImpl) c1.members().get(0)).symbol(); assertThat(a.isEnum()).isFalse(); - JDTMethodSymbol foo = ((JDTMethodSymbol) symbolOf(((MethodTreeImpl) c1.members().get(1)))); + Symbol.MethodSymbol foo = ((MethodTreeImpl) c1.members().get(1)).symbol(); assertThat(foo.isEnum()).isFalse(); ClassTreeImpl e1 = (ClassTreeImpl) cu.types().get(1); - JDTTypeSymbol e1Symbol = ((JDTTypeSymbol) typeOf(e1).symbol()); + Symbol.TypeSymbol e1Symbol = typeOf(e1).symbol(); assertThat(e1Symbol.isEnum()).isTrue(); - JDTVariableSymbol aconst = ((JDTVariableSymbol) symbolOf(((EnumConstantTreeImpl) e1.members().get(0)))); + Symbol aconst = ((EnumConstantTreeImpl) e1.members().get(0)).symbol(); assertThat(aconst.isEnum()).isTrue(); } - private JDTMethodSymbol foo(JavaTree.CompilationUnitTreeImpl cu) { + private Symbol.MethodSymbol foo(JavaTree.CompilationUnitTreeImpl cu) { return method(cu, 0); } - private JDTMethodSymbol bar(JavaTree.CompilationUnitTreeImpl cu) { + private Symbol.MethodSymbol bar(JavaTree.CompilationUnitTreeImpl cu) { return method(cu, 1); } - private JDTMethodSymbol method(JavaTree.CompilationUnitTreeImpl cu, int memberIndex) { + private Symbol.MethodSymbol method(JavaTree.CompilationUnitTreeImpl cu, int memberIndex) { ClassTreeImpl c1 = (ClassTreeImpl) cu.types().get(0); - return ((JDTMethodSymbol) symbolOf(((MethodTreeImpl) c1.members().get(memberIndex)))); + return ((MethodTreeImpl) c1.members().get(memberIndex)).symbol(); } - private JDTVariableSymbol variable(JavaTree.CompilationUnitTreeImpl cu, int memberIndex) { + private Symbol variable(JavaTree.CompilationUnitTreeImpl cu, int memberIndex) { ClassTreeImpl c1 = (ClassTreeImpl) cu.types().get(0); - return ((JDTVariableSymbol) symbolOf(((VariableTreeImpl) c1.members().get(memberIndex)))); + return ((VariableTreeImpl) c1.members().get(memberIndex)).symbol(); } } @@ -759,10 +721,10 @@ int f(int i) { """); ClassTree cClassTree = (ClassTree) cu.types().get(0); - JDTSymbol cSymbol = (JDTSymbol) cClassTree.symbol(); + Symbol cSymbol = (Symbol) cClassTree.symbol(); MethodTree f = (MethodTree) cClassTree.members().get(0); - JDTSymbol fSymbol = (JDTSymbol) f.symbol(); + Symbol fSymbol = (Symbol) 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/model/JTypeSymbolTest.java new file mode 100644 index 00000000000..c6fb66c751d --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java @@ -0,0 +1,138 @@ +/* + * 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 org.junit.jupiter.api.Test; +import org.sonar.java.model.declaration.ClassTreeImpl; +import org.sonar.java.model.declaration.MethodTreeImpl; +import org.sonar.java.model.declaration.VariableTreeImpl; +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.MemberSelectExpressionTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.ReturnStatementTree; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.java.model.JParserTestUtils.sema; +import static org.sonar.java.model.JParserTestUtils.typeOf; + +class JTypeSymbolTest { + + @Test + void superClass() { + var cu = test("interface I {} class C implements I {} class C2 extends C {} class A { Object[] array; }"); + Type object = sema(cu).getClassType("java.lang.Object"); + var i = (ClassTreeImpl) cu.types().get(0); + var c = (ClassTreeImpl) cu.types().get(1); + var c2 = (ClassTreeImpl) cu.types().get(2); + var a = (ClassTreeImpl) cu.types().get(3); + var array = (VariableTreeImpl) a.members().get(0); + + assertThat(object.symbol().superClass()).isNull(); + assertThat(i.symbol().superClass()).isSameAs(object); + assertThat(c.symbol().superClass()).isSameAs(object); + assertThat(c2.symbol().superClass()).isSameAs(c.symbol().type()); + assertThat(array.symbol().type().symbol().superClass()).isSameAs(object); + } + + @Test + void interfaces() { + var cu = test("interface I {} class C implements I {}"); + var i = (ClassTreeImpl) cu.types().get(0); + var c = (ClassTreeImpl) cu.types().get(1); + assertThat(i.symbol().interfaces()).isEmpty(); + assertThat(c.symbol().interfaces()).containsOnly(i.symbol().type()); + } + + @Test + void memberSymbols() { + JavaTree.CompilationUnitTreeImpl cu = test("class C { int f; C() {} class N {} }"); + ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); + VariableTreeImpl f = (VariableTreeImpl) c.members().get(0); + MethodTreeImpl m = (MethodTreeImpl) c.members().get(1); + ClassTreeImpl n = (ClassTreeImpl) c.members().get(2); + assertThat(typeOf(c).symbol().memberSymbols()) + .containsOnly( + f.symbol(), + m.symbol(), + typeOf(n).symbol() + ); + } + + @Test + void superTypesTest() { + JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { } class C2 extends C { }"); + Type javaLangObject = sema(cu).getClassType("java.lang.Object"); + assertThat(javaLangObject.symbol().superTypes()).isEmpty(); + + ClassTreeImpl interfaceI = (ClassTreeImpl) cu.types().get(0); + Symbol.TypeSymbol interfaceITypeSymbol = typeOf(interfaceI).symbol(); + assertThat(interfaceITypeSymbol.superTypes()).isEmpty(); + assertThat(interfaceITypeSymbol.superTypes()).isEmpty(); // repeat call to cover cache + + ClassTreeImpl classC = (ClassTreeImpl) cu.types().get(1); + Symbol.TypeSymbol classCTypeSymbol = typeOf(classC).symbol(); + assertThat(classCTypeSymbol.superTypes()).containsExactly(interfaceITypeSymbol.type(), javaLangObject); + + ClassTreeImpl classC2 = (ClassTreeImpl) cu.types().get(2); + Symbol.TypeSymbol classC2TypeSymbol = typeOf(classC2).symbol(); + assertThat(classC2TypeSymbol.superTypes()).containsExactly(classCTypeSymbol.type(), interfaceITypeSymbol.type(), javaLangObject); + } + + @Test + void outermostClassTest() { + JavaTree.CompilationUnitTreeImpl cu = test("class C { class N {} }"); + ClassTreeImpl outerClass = (ClassTreeImpl) cu.types().get(0); + ClassTreeImpl innerClass = (ClassTreeImpl) outerClass.members().get(0); + Symbol.TypeSymbol innerClassSymbol = typeOf(innerClass).symbol(); + Symbol.TypeSymbol outerClassSymbol = typeOf(outerClass).symbol(); + assertThat(innerClassSymbol.outermostClass()).isSameAs(outerClassSymbol); + assertThat(innerClassSymbol.outermostClass()).isSameAs(outerClassSymbol); + } + + @Test + void isAnnotationTest() { + Symbol.TypeSymbol annotationSymbol = typeSymbolFromClassText("@interface A { }"); + assertThat(annotationSymbol.isAnnotation()).isTrue(); + Symbol.TypeSymbol classSymbol = typeSymbolFromClassText("class C { }"); + assertThat(classSymbol.isAnnotation()).isFalse(); + } + + @Test + void isEffectivelyFinalTest() { + var cu = test("class C { String m() { return super.toString(); } }"); + var c = (ClassTreeImpl) cu.types().get(0); + var m = (MethodTreeImpl) c.members().get(0); + var statement = (ReturnStatementTree) m.block().body().get(0); + var invocation = (MethodInvocationTree) statement.expression(); + var select = (MemberSelectExpressionTree) invocation.methodSelect(); + var keywordSuper = (IdentifierTree) select.expression(); + + assertThat(((Symbol.VariableSymbol) keywordSuper.symbol()).isEffectivelyFinal()).isFalse(); + } + + private static Symbol.TypeSymbol typeSymbolFromClassText(String classText) { + var cu = test(classText); + return ((ClassTreeImpl) cu.types().get(0)).symbol(); + } + + private static JavaTree.CompilationUnitTreeImpl test(String source) { + return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); + } + +} diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JTypeTest.java b/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java similarity index 68% rename from java-frontend/src/test/java/org/sonar/java/jdt/JTypeTest.java rename to java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java index d8a7f837048..eaf7396e3d8 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JTypeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java @@ -14,45 +14,31 @@ * 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; +package org.sonar.java.model; import java.util.Objects; import java.util.stream.Stream; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; 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; import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.java.model.statement.ExpressionStatementTreeImpl; import org.sonar.java.model.statement.ReturnStatementTreeImpl; -import org.sonar.java.testing.ThreadLocalLogTester; +import org.sonar.plugins.java.api.semantic.Sema; import org.sonar.plugins.java.api.semantic.Type; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import static org.sonar.java.model.JParserTestUtils.typeOf; import static org.sonar.java.model.assertions.TypeAssert.assertThat; class JTypeTest { - @RegisterExtension - public ThreadLocalLogTester logTester = new ThreadLocalLogTester().setLevel(Level.DEBUG); - @Test void isArray() { assertThat(type("int[]").isArray()) @@ -99,8 +85,7 @@ void isParametrizedType() { ExpressionStatementTreeImpl s = (ExpressionStatementTreeImpl) Objects.requireNonNull(m.block()).body().get(0); AbstractTypedTree e = Objects.requireNonNull((AbstractTypedTree) s.expression()); - assertThat(((JDTType) typeOf(e)).isParameterized()) - .isEqualTo(e.symbolType().isParameterized()) + assertThat(typeOf(e).isParameterized()) .isTrue(); } @@ -112,12 +97,10 @@ void typeArguments() { ExpressionStatementTreeImpl s = (ExpressionStatementTreeImpl) Objects.requireNonNull(m.block()).body().get(0); AbstractTypedTree e = Objects.requireNonNull((AbstractTypedTree) s.expression()); - assertThat(((JDTType) typeOf(e)).typeArguments()) - .hasToString(e.symbolType().typeArguments().toString()) + assertThat(typeOf(e).typeArguments()) .hasToString("[Integer, String]"); - assertThat(((JDTType) typeOf(c)).typeArguments()) - .isEqualTo(c.symbol().type().typeArguments()) + assertThat(typeOf(c).typeArguments()) .isEmpty(); } @@ -143,14 +126,14 @@ void isPrimitive() { } @Test - void primitiveType(){ + void primitiveType() { Type byteType = type("java.lang.Byte"); assertThat(byteType.primitiveType()).isEqualTo(type("byte")); assertThat(byteType.primitiveType()).isNotEqualTo(type("boolean")); } @Test - void primitiveWrapperType(){ + void primitiveWrapperType() { Type booleanType = type("boolean"); assertThat(booleanType.primitiveWrapperType()).isEqualTo(type("java.lang.Boolean")); assertThat(booleanType.primitiveWrapperType()).isNotEqualTo(type("java.lang.Byte")); @@ -169,32 +152,7 @@ void non_primitive_wrapper_tpe_has_no_primitive_wrapper() { } @Test - void primitiveWrapperType_returns_null_when_semantic_cannot_resolve_wrapper_type() { - // Sema that can resolve primitives, but fails to resolve wrapper types, which can happen when the classpath is not correctly set up - var brokenSema = spy(sema); - when(brokenSema.resolveType("java.lang.Byte")).thenReturn(null); - - var primitiveBinding = sema.resolveType("byte"); - var primitiveType = new JDTType(brokenSema, primitiveBinding); - - assertThat(primitiveType.primitiveWrapperType()).isNull(); - } - - @Test - void primitiveType_returns_null_when_semantic_cannot_resolve_primitive_type() { - // Sema that can resolve wrapper types, but fails to resolve a primitive, this should never happen in practice, - // but we want to make sure it doesn't throw an exception and returns null instead - var brokenSema = spy(sema); - when(brokenSema.resolveType("byte")).thenReturn(null); - - var wrapperBinding = sema.resolveType("java.lang.Byte"); - var wrapperType = new JDTType(brokenSema, wrapperBinding); - - assertThat(wrapperType.primitiveType()).isNull(); - } - - @Test - void declaringType(){ + void declaringType() { Type byteType = type("java.lang.Byte"); assertThat(byteType.declaringType()).isEqualTo(byteType); assertThat(byteType.declaringType()).isNotEqualTo(type("java.lang.Boolean")); @@ -239,11 +197,10 @@ 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()); - JDTType parameterizedType = ((JDTType) typeOf(e)); + Type parameterizedType = typeOf(e); assertThat(parameterizedType) - .hasSameNameAs(e.symbolType()) - .hasSameNameAs(((JDTTypeSymbol) typeOf(e).symbol()).type()) + .hasSameNameAs(typeOf(e).symbol().type()) .is("java.util.List") .hasName("List"); } @@ -254,10 +211,9 @@ 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()); - JDTType typeVariable = ((JDTType) typeOf(e)); + Type typeVariable = typeOf(e); assertThat(typeVariable) - .hasSameNameAs(e.symbolType()) - .hasSameNameAs(((JDTTypeSymbol) typeOf(e).symbol()).type()) + .hasSameNameAs(typeOf(e).symbol().type()) .hasName("T") .is(e.symbolType().fullyQualifiedName()) .is("T"); @@ -265,9 +221,8 @@ void name_of_type_variable() { @Test void symbol() { - ITypeBinding typeBinding = Objects.requireNonNull(sema.resolveType("java.lang.Object")); - assertThat(sema.type(typeBinding).symbol()) - .isSameAs(sema.typeSymbol(typeBinding)); + assertThat(type("java.lang.Object").symbol().type()) + .isSameAs(type("java.lang.Object")); } @Test @@ -278,7 +233,7 @@ void erasure() { @Test void elementType() { - assertThat(type("int[][]").elementType()) + assertThat(((Type.ArrayType) type("int[][]")).elementType()) .isSameAs(type("int[]")); } @@ -291,7 +246,6 @@ Object foo(java.util.List list) { } } """); - cu.types().get(0); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); ReturnStatementTreeImpl s = (ReturnStatementTreeImpl) Objects.requireNonNull(m.block()).body().get(0); @@ -312,13 +266,12 @@ void null_type() { AbstractTypedTree primitive = (AbstractTypedTree) m.parameters().get(0).type(); AbstractTypedTree array = (AbstractTypedTree) m.parameters().get(1).type(); - JDTType nullType = ((JDTType) typeOf(e)); - JDTType classType = ((JDTType) typeOf(c)); - JDTType primitiveType = ((JDTType) typeOf(primitive)); - JDTType arrayType = ((JDTType) typeOf(array)); + Type nullType = typeOf(e); + Type classType = typeOf(c); + Type primitiveType = typeOf(primitive); + Type arrayType = typeOf(array); assertThat(nullType) - .hasSameNameAs(e.symbolType()) .hasName("") .is(e.symbolType().fullyQualifiedName()) .is("") @@ -327,22 +280,6 @@ void null_type() { .isNotSubtypeOf(primitiveType); } - @Test - void is_subtype_of_should_not_throw_NPE() { - 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")); - - JDTType stringType = new JDTType(sema, brokenStringBinding); - // should catch the NullPointerException - assertThat(stringType.isSubtypeOf(objectType)).isFalse(); - - assertThat(logTester.logs(Level.DEBUG)) - .containsExactly("NullPointerException while resolving isSubTypeCompatible()"); - } - @Test void wildcard() { JavaTree.CompilationUnitTreeImpl cu = test("class C { C f; }"); @@ -352,9 +289,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); - JDTType wildcardType1 = ((JDTType) typeOf(w1)); - JDTType wildcardType2 = ((JDTType) typeOf(w2)); - JDTType wildcardType3 = ((JDTType) typeOf(w3)); + Type wildcardType1 = typeOf(w1); + Type wildcardType2 = typeOf(w2); + Type wildcardType3 = typeOf(w3); assertThat(wildcardType1) .isEqualTo(wildcardType2) @@ -365,27 +302,15 @@ private static JavaTree.CompilationUnitTreeImpl test(String source) { return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); } - private JDTType type(String name) { - ITypeBinding typeBinding = Objects.requireNonNull(sema.resolveType(name)); - return sema.type(typeBinding); + private Type type(String name) { + return sema.getClassType(name); } - private JDTSema sema; + private Sema sema; @BeforeEach void setup() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - astParser.setEnvironment( - new String[]{}, - new String[]{}, - new String[]{}, - true - ); - astParser.setResolveBindings(true); - astParser.setUnitName("File.java"); - astParser.setSource("".toCharArray()); - AST ast = astParser.createAST(null).getAST(); - sema = new JDTSema(ast); + sema = JParserTestUtils.sema(JParserTestUtils.parse("")); } } diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java similarity index 91% rename from java-frontend/src/test/java/org/sonar/java/jdt/JUtilsTest.java rename to java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java index ef99c658a0a..600acfc56f9 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java @@ -14,17 +14,12 @@ * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ -package org.sonar.java.jdt; +package org.sonar.java.model; 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; @@ -33,6 +28,7 @@ import org.sonar.java.model.expression.TypeCastExpressionTreeImpl; import org.sonar.java.model.statement.ExpressionStatementTreeImpl; import org.sonar.java.model.statement.ReturnStatementTreeImpl; +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.Type; @@ -42,21 +38,20 @@ import org.sonar.plugins.java.api.tree.TypeParameterTree; import static org.assertj.core.api.Assertions.assertThat; -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.mockito.Mockito.mock; +import static org.sonar.java.model.JParserTestUtils.sema; import static org.sonar.java.model.JParserTestUtils.typeOf; class JUtilsTest { - static final JDTSema SEMA = sema(test("")); - private static final Type OBJECT_TYPE = SEMA.type(SEMA.resolveType("java.lang.Object")); + private static final Sema SEMA = sema(test("")); + private static final Type OBJECT_TYPE = SEMA.getClassType("java.lang.Object"); @Nested class PrimitiveType { @Test void java_lang_Byte() { - Type wrapperType = SEMA.type(SEMA.resolveType("java.lang.Byte")); + Type wrapperType = SEMA.getClassType("java.lang.Byte"); Type primitiveType = wrapperType.primitiveType(); assertThat(primitiveType).isNotNull(); @@ -73,7 +68,7 @@ void object_is_not_a_primitive_type() { class PrimitiveWrapperType { @Test void java_lang_Byte() { - Type primitiveType = SEMA.type(SEMA.resolveType("byte")); + Type primitiveType = SEMA.getClassType("byte"); Type wrapperType = primitiveType.primitiveWrapperType(); assertThat(wrapperType).isNotNull(); @@ -88,7 +83,7 @@ void object_is_not_a_primitive_wrapper_type() { @Nested class IsPrimitiveWrapper { - private final Type primitiveType = SEMA.type(SEMA.resolveType("byte")); + private final Type primitiveType = SEMA.getClassType("byte"); @Test void wrapper() { @@ -105,7 +100,7 @@ void not_a_wrapper() { @Test void not_a_class() { - Type objectArrayType = SEMA.type(SEMA.resolveType("java.lang.Object[]")); + Type objectArrayType = SEMA.getClassType("java.lang.Object[]"); assertThat(objectArrayType.isPrimitiveWrapper()).isFalse(); } } @@ -114,7 +109,7 @@ void not_a_class() { class WrapTypeIfPrimitiveTest { @Test void prim_to_wrapped() { - Type primitiveType = SEMA.type(SEMA.resolveType("byte")); + Type primitiveType = SEMA.getClassType("byte"); Type wrappedType = JUtils.wrapTypeIfPrimitive(primitiveType); assertThat(wrappedType).isNotNull(); assertThat(wrappedType.isPrimitive()).isFalse(); @@ -123,7 +118,7 @@ void prim_to_wrapped() { @Test void already_wrapped() { - Type boxedType = SEMA.type(SEMA.resolveType("java.lang.Byte")); + Type boxedType = SEMA.getClassType("java.lang.Byte"); Type wrappedType = JUtils.wrapTypeIfPrimitive(boxedType); assertThat(wrappedType).isNotNull(); assertThat(wrappedType.isPrimitive()).isFalse(); @@ -132,7 +127,7 @@ void already_wrapped() { @Test void unrelated() { - Type otherType = SEMA.type(SEMA.resolveType("java.lang.Object")); + Type otherType = SEMA.getClassType("java.lang.Object"); Type wrappedType = JUtils.wrapTypeIfPrimitive(otherType); assertThat(wrappedType).isNotNull(); assertThat(wrappedType.isPrimitive()).isFalse(); @@ -149,23 +144,20 @@ class IsNullType { void nullType() { ReturnStatementTreeImpl s = (ReturnStatementTreeImpl) m1.block().body().get(0); AbstractTypedTree e = (AbstractTypedTree) s.expression(); - assertThat(((JDTType) typeOf(e)).isNullType()) - .isEqualTo(e.symbolType().isNullType()) + assertThat(typeOf(e).isNullType()) .isTrue(); } @Test void a_non_null_type_is_not_null_type() { - assertThat(sema(cu).type(methodBinding(m1).getReturnType()).isNullType()) - .isEqualTo(m1.returnType().symbolType().isNullType()) + assertThat(m1.symbol().returnType().type().isNullType()) .isFalse(); } @Test void unresolved_type_is_not_null_type() { MethodTreeImpl m2 = nthMethod(c, 1); - assertThat(sema(cu).type(methodBinding(m2).getReturnType()).isNullType()) - .isEqualTo(m2.returnType().symbolType().isNullType()) + assertThat(m2.symbol().returnType().type().isNullType()) .isFalse(); } } @@ -187,23 +179,20 @@ class C { @Test void intersection_type() { TypeCastExpressionTreeImpl e = (TypeCastExpressionTreeImpl) f.initializer(); - assertThat(JUtils.isIntersectionType(((JDTType) typeOf(e)))) - .isEqualTo(JUtils.isIntersectionType(e.symbolType())) + assertThat(JUtils.isIntersectionType(typeOf(e))) .isTrue(); } @Test void non_intersection_type() { - assertThat(JUtils.isIntersectionType(((JDTType) symbolOf(f).type()))) - .isEqualTo(JUtils.isIntersectionType(f.symbol().type())) + assertThat(JUtils.isIntersectionType(f.symbol().type())) .isFalse(); } @Test void unresolved_type_is_not_an_intersection_type() { VariableTreeImpl u = nthField(c, 1); - assertThat(JUtils.isIntersectionType(((JDTType) symbolOf(u).type()))) - .isEqualTo(JUtils.isIntersectionType(u.symbol().type())) + assertThat(JUtils.isIntersectionType(u.symbol().type())) .isFalse(); } } @@ -216,8 +205,7 @@ class IsTypeVar { @Test void type_var() { VariableTreeImpl t = firstField(c); - assertThat(((JDTType) symbolOf(t).type()).isTypeVar()) - .isEqualTo(t.symbol().type().isTypeVar()) + assertThat(t.symbol().type().isTypeVar()) .isTrue(); } @@ -229,8 +217,7 @@ void simple_type_is_not_a_type_var() { @Test void unresolved_type_is_not_a_type_var() { VariableTreeImpl u = nthField(c, 1); - assertThat(((JDTType) symbolOf(u).type()).isTypeVar()) - .isEqualTo(u.symbol().type().isTypeVar()) + assertThat(u.symbol().type().isTypeVar()) .isFalse(); } } @@ -393,22 +380,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 = ((JDTVariableSymbol) symbolOf(firstField(specificC))); + Symbol.VariableSymbol shortConstant = (Symbol.VariableSymbol) firstField(specificC).symbol(); assertThat(shortConstant.constantValue().orElseThrow(AssertionError::new)) .isInstanceOf(Integer.class) .isEqualTo(42); - Symbol.VariableSymbol charConstant = ((JDTVariableSymbol) symbolOf(nthField(specificC, 1))); + Symbol.VariableSymbol charConstant = (Symbol.VariableSymbol) nthField(specificC, 1).symbol(); assertThat(charConstant.constantValue().orElseThrow(AssertionError::new)) .isInstanceOf(Integer.class) .isEqualTo(42); - Symbol.VariableSymbol byteConstant = ((JDTVariableSymbol) symbolOf(nthField(specificC, 2))); + Symbol.VariableSymbol byteConstant = (Symbol.VariableSymbol) nthField(specificC, 2).symbol(); assertThat(byteConstant.constantValue().orElseThrow(AssertionError::new)) .isInstanceOf(Integer.class) .isEqualTo(42); - Symbol.VariableSymbol booleanConstant = ((JDTVariableSymbol) symbolOf(nthField(specificC, 3))); + Symbol.VariableSymbol booleanConstant = (Symbol.VariableSymbol) nthField(specificC, 3).symbol(); assertThat(booleanConstant.constantValue().orElseThrow(AssertionError::new)) .isInstanceOf(Boolean.class) .isEqualTo(Boolean.FALSE); @@ -714,13 +701,9 @@ void isParametrizedMethod() { ExpressionStatementTreeImpl s = (ExpressionStatementTreeImpl) m.block().body().get(0); MethodInvocationTreeImpl methodInvocation = (MethodInvocationTreeImpl) s.expression(); - assertThat(((JDTMethodSymbol) symbolOf(m)).isParametrizedMethod()) - .isEqualTo(m.symbol().isParametrizedMethod()) - .isEqualTo(methodBinding(m).isGenericMethod()) + assertThat(m.symbol().isParametrizedMethod()) .isTrue(); - assertThat(((JDTMethodSymbol) symbolOf(methodInvocation)).isParametrizedMethod()) - .isEqualTo(methodInvocation.methodSymbol().isParametrizedMethod()) - .isEqualTo(methodBinding(methodInvocation).isParameterizedMethod()) + assertThat(methodInvocation.methodSymbol().isParametrizedMethod()) .isTrue(); } @@ -1029,7 +1012,7 @@ void parameterAnnotations() { @Test void test_has_unknown_type_in_hierarchy_with_unexpected_null_owner() { - JDTInitializerBlockSymbol method = new JDTInitializerBlockSymbol(null, true); + Symbol.MethodSymbol method = mock(Symbol.MethodSymbol.class); assertThat(JUtils.hasUnknownTypePreventingOverrideResolution(method)).isTrue(); } diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JVariableSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java similarity index 86% rename from java-frontend/src/test/java/org/sonar/java/jdt/JVariableSymbolTest.java rename to java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java index 4edd8261b08..35b49c5560a 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JVariableSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JVariableSymbolTest.java @@ -14,21 +14,19 @@ * 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; +package org.sonar.java.model; 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; +import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.BlockTree; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.LambdaExpressionTree; import org.sonar.plugins.java.api.tree.VariableTree; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.java.model.JParserTestUtils.symbolOf; class JVariableSymbolTest { @@ -38,10 +36,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); - JDTVariableSymbol variableSymbol = ((JDTVariableSymbol) symbolOf(localVariable)); + Symbol variableSymbol = localVariable.symbol(); assertThat(variableSymbol.isLocalVariable()).isTrue(); VariableTreeImpl classVariable = (VariableTreeImpl) c.members().get(1); - variableSymbol = ((JDTVariableSymbol) symbolOf(classVariable)); + variableSymbol = classVariable.symbol(); assertThat(variableSymbol.isLocalVariable()).isFalse(); } @@ -64,11 +62,11 @@ void isParameter() { MethodTreeImpl method = (MethodTreeImpl) c.members().get(0); VariableTreeImpl localVariable = (VariableTreeImpl) method.block().body().get(0); - JDTVariableSymbol variableSymbol = ((JDTVariableSymbol) symbolOf(localVariable)); + Symbol variableSymbol = localVariable.symbol(); assertThat(variableSymbol.isParameter()).isFalse(); VariableTreeImpl parameter = (VariableTreeImpl) method.parameters().get(0); - variableSymbol = ((JDTVariableSymbol) symbolOf(parameter)); + variableSymbol = parameter.symbol(); assertThat(variableSymbol.isParameter()).isTrue(); } diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JMethodSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/MethodSymbolTest.java similarity index 63% rename from java-frontend/src/test/java/org/sonar/java/jdt/JMethodSymbolTest.java rename to java-frontend/src/test/java/org/sonar/java/model/MethodSymbolTest.java index b659e5daa04..4144502c806 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JMethodSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/MethodSymbolTest.java @@ -14,25 +14,19 @@ * 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; +package org.sonar.java.model; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Objects; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; 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; @@ -40,32 +34,20 @@ import org.sonar.java.model.statement.ExpressionStatementTreeImpl; import org.sonar.java.model.statement.ReturnStatementTreeImpl; import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.semantic.Sema; 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; import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.StatementTree; -import org.sonar.plugins.java.api.tree.VariableTree; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; -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.sema; import static org.sonar.java.model.JParserTestUtils.typeOf; -class JMethodSymbolTest { +class MethodSymbolTest { @Test void annotation_default_values_use_semantic_api_types() { @@ -112,15 +94,15 @@ 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); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.parameterTypes()) - .containsOnly(((JDTType) typeOf(c))); + .containsOnly(typeOf(c)); assertThat(symbol.declarationParameters()) .containsExactly(m.parameters().get(0).symbol()); assertThat(symbol.returnType()) - .isSameAs(((JDTTypeSymbol) typeOf(c).symbol())); + .isSameAs(typeOf(c).symbol()); assertThat(symbol.thrownTypes()) - .containsOnly(sema(cu).type(Objects.requireNonNull(sema(cu).resolveType("java.lang.Exception")))); + .containsOnly(sema(cu).getClassType("java.lang.Exception")); } @Test @@ -129,24 +111,23 @@ void test_with_generics() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); Symbol.MethodSymbol declarationSymbol = m.symbol(); - JDTType declarationParameterType = sema(cu).type(Objects.requireNonNull((methodBinding(m).getParameterTypes()[0]))); + Type declarationParameterType = m.parameters().get(0).type().symbolType(); assertThat(declarationSymbol.parameterTypes()) .containsOnly(declarationParameterType); Symbol declarationParameterSymbol = m.parameters().get(0).symbol(); assertThat(declarationSymbol.declarationParameters()) .containsExactly(declarationParameterSymbol); - MethodInvocationTree invocationWithString = (MethodInvocationTree) ((ExpressionStatementTree) m.block().body().get(0)).expression(); - Symbol.MethodSymbol invocationSymbol = invocationWithString.methodSymbol(); + MethodInvocationTree invocationWithInteger = (MethodInvocationTree) ((ExpressionStatementTree) m.block().body().get(0)).expression(); + Symbol.MethodSymbol invocationSymbol = invocationWithInteger.methodSymbol(); // Parameter types are not always the same as the one from the declaration: it can be the actual type and not the generic (Integer instead of T). Type invocationArgument = invocationSymbol.parameterTypes().get(0); assertThat(invocationArgument) .isNotEqualTo(declarationParameterType); assertThat(invocationArgument.name()).isEqualTo("Integer"); // However, the symbols of the declaration should be the same - invocationSymbol.declarationParameters().containsAll( - declarationSymbol.declarationParameters() - ); + assertThat(invocationSymbol.declarationParameters()) + .containsExactlyElementsOf(declarationSymbol.declarationParameters()); } @Test @@ -171,16 +152,14 @@ void test_declaration_not_in_code() { Symbol symbolParam1 = symbol1.declarationParameters().get(0); Symbol symbolParam2 = symbol2.declarationParameters().get(0); - assertThat(symbolParam1).isInstanceOf(JDTVariableSymbol.ParameterPlaceholderSymbol.class) - .isInstanceOf(Symbol.VariableSymbol.class); - assertThat(symbolParam2).isInstanceOf(JDTVariableSymbol.ParameterPlaceholderSymbol.class) - .isInstanceOf(Symbol.VariableSymbol.class); + assertThat(symbolParam1).isInstanceOf(Symbol.VariableSymbol.class); + assertThat(symbolParam2).isInstanceOf(Symbol.VariableSymbol.class); assertThat(symbolParam1.isVariableSymbol()).isTrue(); assertThat(symbolParam2.isVariableSymbol()).isTrue(); - JDTVariableSymbol.ParameterPlaceholderSymbol parameterSymbol1 = (JDTVariableSymbol.ParameterPlaceholderSymbol) symbolParam1; - JDTVariableSymbol.ParameterPlaceholderSymbol parameterSymbol2 = (JDTVariableSymbol.ParameterPlaceholderSymbol) symbolParam2; + Symbol.VariableSymbol parameterSymbol1 = (Symbol.VariableSymbol) symbolParam1; + Symbol.VariableSymbol parameterSymbol2 = (Symbol.VariableSymbol) symbolParam2; assertThat(parameterSymbol1).isSameAs(parameterSymbol2); @@ -242,7 +221,7 @@ void testSingleInheritance() { + "class Clazz implements A { public void a() {} }"); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(1); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.overriddenSymbols()).containsOnly(retrieveMethodSymbol("A", "a", cu)); } @@ -257,7 +236,7 @@ class C extends B { public void a() {} } """); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(3); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.overriddenSymbols()).containsExactly( retrieveMethodSymbol("B", "a", cu), @@ -270,7 +249,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); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.overriddenSymbols()).containsExactly(objectEqualsMethod(sema(cu))); } @@ -280,16 +259,15 @@ 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); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.overriddenSymbols()).containsExactly(objectEqualsMethod(sema(cu))); } - private static JDTMethodSymbol objectEqualsMethod(JDTSema sema) { - return Arrays.stream(sema.resolveType("java.lang.Object").getDeclaredMethods()) - .filter(method -> "equals".equals(method.getName())) + private static Symbol.MethodSymbol objectEqualsMethod(Sema sema) { + return sema.getClassType("java.lang.Object").symbol().lookupSymbols("equals").stream() + .map(Symbol.MethodSymbol.class::cast) .findFirst() - .map(sema::methodSymbol) .orElseThrow(() -> new IllegalStateException("Could not find Object#equals")); } @@ -302,7 +280,7 @@ class C implements A, B { public void a() { } } """); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(2); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.overriddenSymbols()).containsExactly( retrieveMethodSymbol("A", "a", cu), @@ -316,7 +294,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); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.overriddenSymbols()).containsExactly( retrieveMethodSymbol("KnownInterface", "a", cu)); @@ -328,32 +306,10 @@ void testUnknownExtends() { + "class B extends Unknown { public void a() { } }"); ClassTreeImpl a = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) a.members().get(0); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.overriddenSymbols()).isEmpty(); } - @Test - void testBindingReturnNullSuperClass() { - JavaTree.CompilationUnitTreeImpl cu = test("" - + "class A { public void a() { } }"); - ClassTreeImpl a = (ClassTreeImpl) cu.types().get(0); - MethodTreeImpl m = (MethodTreeImpl) a.members().get(0); - // 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. - 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". - when(iTypeBinding.getSuperclass()).thenReturn(null); - ITypeBinding[] interfaces = {null}; - when(iTypeBinding.getInterfaces()).thenReturn(interfaces); - - Collection overrides = new ArrayList<>(); - assertDoesNotThrow(() -> - symbol.findOverridesInParentTypes(overrides, methodBinding -> false, iTypeBinding)); - assertThat(overrides).isEmpty(); - } - @Test void testMultipleInheritanceWithExtensionOfObject() { JavaTree.CompilationUnitTreeImpl cu = test(""" @@ -364,7 +320,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); - JDTMethodSymbol symbol = ((JDTMethodSymbol) symbolOf(m)); + Symbol.MethodSymbol symbol = m.symbol(); assertThat(symbol.overriddenSymbols()).containsExactly( objectEqualsMethod(sema(cu)), @@ -372,7 +328,7 @@ class Clazz implements A, C { public boolean equals(Object other) { return false retrieveMethodSymbol("B", "equals", cu)); } - private static JDTMethodSymbol retrieveMethodSymbol(String className, String methodName, CompilationUnitTreeImpl cu) { + private static Symbol.MethodSymbol retrieveMethodSymbol(String className, String methodName, CompilationUnitTreeImpl cu) { MethodMatchers methodMatcher = MethodMatchers.create() .ofTypes(className) .names(methodName) @@ -385,7 +341,7 @@ private static JDTMethodSymbol retrieveMethodSymbol(String className, String met .map(Symbol.TypeSymbol::memberSymbols) .flatMap(Collection::stream) .filter(methodMatcher::matches) - .map(JDTMethodSymbol.class::cast) + .map(Symbol.MethodSymbol.class::cast) .findFirst() .orElseThrow(() -> new IllegalStateException("No method could be found with the given name in the given class")); } @@ -398,87 +354,61 @@ 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(((JDTMethodSymbol) symbolOf(constructor)).signature()) - .isEqualTo(constructor.symbol().signature()) + assertThat(constructor.symbol().signature()) .isEqualTo("org.example.C#()V"); - assertThat(((JDTMethodSymbol) symbolOf(method)).signature()) - .isEqualTo(method.symbol().signature()) + assertThat(method.symbol().signature()) .isEqualTo("org.example.C#m(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - assertThat(((JDTMethodSymbol) symbolOf(methodInvocation)).signature()) + assertThat(methodInvocation.methodSymbol().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(){ - 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(); - jms = getJMethodSymbolFromClassText("class C { private C m() { return null; } }"); - assertThat(jms.isOverridable()).isFalse(); - jms = getJMethodSymbolFromClassText("class C { static C m() { return null; } }"); - assertThat(jms.isOverridable()).isFalse(); - jms = getJMethodSymbolFromClassText("class C { final C m() { return null; } }"); - assertThat(jms.isOverridable()).isFalse(); - jms = getJMethodSymbolFromClassText("final class C { C m() { return null; } }"); - assertThat(jms.isOverridable()).isFalse(); + void isOverridableTest() { + Symbol.MethodSymbol symbol = methodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(symbol.isOverridable()).isTrue(); + symbol = methodSymbolFromClassText("class C { private C m() { return null; } }"); + assertThat(symbol.isOverridable()).isFalse(); + symbol = methodSymbolFromClassText("class C { static C m() { return null; } }"); + assertThat(symbol.isOverridable()).isFalse(); + symbol = methodSymbolFromClassText("class C { final C m() { return null; } }"); + assertThat(symbol.isOverridable()).isFalse(); + symbol = methodSymbolFromClassText("final class C { C m() { return null; } }"); + assertThat(symbol.isOverridable()).isFalse(); } @Test - void isParametrizedMethodTest(){ - 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(); - jms = getJMethodSymbolFromClassText("class C { Collection m(T t) { return null; } }"); - assertThat(jms.isParametrizedMethod()).isTrue(); - jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }", true); - assertThat(jms.isParametrizedMethod()).isFalse(); + void isParametrizedMethodTest() { + Symbol.MethodSymbol symbol = methodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(symbol.isParametrizedMethod()).isFalse(); + symbol = methodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(symbol.isParametrizedMethod()).isTrue(); + symbol = methodSymbolFromClassText("class C { Collection m(T t) { return null; } }"); + assertThat(symbol.isParametrizedMethod()).isTrue(); } @Test - void isDefaultMethodTest(){ - 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(); - jms = getJMethodSymbolFromClassText("interface I { default C m() { return null; } }", true); - assertThat(jms.isDefaultMethod()).isFalse(); + void isDefaultMethodTest() { + Symbol.MethodSymbol symbol = methodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(symbol.isDefaultMethod()).isFalse(); + symbol = methodSymbolFromClassText("interface I { default C m() { return null; } }"); + assertThat(symbol.isDefaultMethod()).isTrue(); } @Test - void isSynchronizedMethodTest(){ - 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(); - jms = getJMethodSymbolFromClassText("class C { synchronized C m() { return null; } }", true); - assertThat(jms.isSynchronizedMethod()).isFalse(); + void isSynchronizedMethodTest() { + Symbol.MethodSymbol symbol = methodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(symbol.isSynchronizedMethod()).isFalse(); + symbol = methodSymbolFromClassText("class C { synchronized C m() { return null; } }"); + assertThat(symbol.isSynchronizedMethod()).isTrue(); } @Test - void isVarArgsTest(){ - 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(); - jms = getJMethodSymbolFromClassText("class C { C m(String... s) { return null; } }", true); - assertThat(jms.isVarArgsMethod()).isFalse(); - } - - @Test - void support_unexpected_IMethodBinding_null_return_type() { - JavaTree.CompilationUnitTreeImpl cu = test("class C { C m() { return null; } }"); - ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); - MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - // simulate broken semantic with getReturnType() == null - IMethodBinding brokenMethodBinding = spy(methodBinding(m)); - when(brokenMethodBinding.getReturnType()).thenReturn(null); - JDTMethodSymbol methodSymbol = new JDTMethodSymbol(sema(cu), brokenMethodBinding); - assertThat(methodSymbol.returnType().isUnknown()).isTrue(); - assertThat(methodSymbol.isNativeMethod()).isFalse(); - assertThat(methodSymbol.metadata()).isSameAs(Symbols.EMPTY_METADATA); + void isVarArgsTest() { + Symbol.MethodSymbol symbol = methodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(symbol.isVarArgsMethod()).isFalse(); + symbol = methodSymbolFromClassText("class C { C m(String... s) { return null; } }"); + assertThat(symbol.isVarArgsMethod()).isTrue(); } @Test @@ -496,25 +426,16 @@ public static void f() { MethodTreeImpl m = (MethodTreeImpl) c.members().get(1); ExpressionStatementTree statementTree = ((ExpressionStatementTreeImpl) m.block().body().get(0)); NewClassTreeImpl newClassExpression = ((NewClassTreeImpl) statementTree.expression()); - JDTMethodSymbol symbol = ((JDTMethodSymbol) newClassExpression.methodSymbol()); + Symbol.MethodSymbol symbol = newClassExpression.methodSymbol(); assertThat(symbol.declarationParameters()).hasSize(6); } - private static JDTMethodSymbol getJMethodSymbolFromClassText(String classText){ - return getJMethodSymbolFromClassText(classText, false); - } - private static JDTMethodSymbol getJMethodSymbolFromClassText(String classText, boolean brokenSemantic){ - JavaTree.CompilationUnitTreeImpl cu = test(classText); + private static Symbol.MethodSymbol methodSymbolFromClassText(String classText) { + CompilationUnitTreeImpl cu = test(classText); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - if(brokenSemantic){ - IMethodBinding brokenMethodBinding = spy(methodBinding(m)); - when(brokenMethodBinding.getReturnType()).thenReturn(null); - when(brokenMethodBinding.isRecovered()).thenReturn(true); - return new JDTMethodSymbol(sema(cu), brokenMethodBinding); - } - return new JDTMethodSymbol(sema(cu), methodBinding(m)); + return m.symbol(); } private static CompilationUnitTreeImpl test(String source) { @@ -529,17 +450,17 @@ class A { void bar() { } } """); - private final ClassTreeImpl a = firstClass(cu); + private final ClassTreeImpl a = (ClassTreeImpl) cu.types().get(0); @Test void isNativeMethod() { - MethodTreeImpl nativeMethod = firstMethod(a); + MethodTreeImpl nativeMethod = (MethodTreeImpl) a.members().get(0); assertThat(nativeMethod.symbol().isNativeMethod()).isTrue(); } @Test void not_native() { - MethodTreeImpl nonNativeMethod = nthMethod(a, 1); + MethodTreeImpl nonNativeMethod = (MethodTreeImpl) a.members().get(1); assertThat(nonNativeMethod.symbol().isNativeMethod()).isFalse(); } @@ -548,26 +469,4 @@ void unknown_method_is_not_native() { assertThat(Symbol.MethodSymbol.UNKNOWN_METHOD.isNativeMethod()).isFalse(); } } - - @Test - void isLambda() { - JavaTree.CompilationUnitTreeImpl cu = test(""" - class A { - void foo() { } - java.util.function.IntConsumer bar = i -> { }; - } - """); - ClassTreeImpl a = firstClass(cu); - - MethodTreeImpl foo = firstMethod(a); - 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 = (JDTMethodSymbol) initializer.symbol(); - assertTrue(lambdaSymbol.isLambda()); - assertThat(lambdaSymbol.toString()).isNotEmpty(); - } } From 434330703538a6286bb43e649574435f71a44625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 11 Nov 2026 16:03:18 +0100 Subject: [PATCH 7/8] Fix the QG --- .../src/test/java/org/sonar/java/jdt/JDTParserTest.java | 1 - .../java/org/sonar/java/model/JParserSemanticTest.java | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserTest.java b/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserTest.java index f6e36146be3..022fcf6d6b4 100644 --- a/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserTest.java +++ b/java-frontend/src/test/java/org/sonar/java/jdt/JDTParserTest.java @@ -54,7 +54,6 @@ 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.JParserTestUtils.DEFAULT_CLASSPATH; -import static org.sonar.java.model.JParserTestUtils.parse; class JDTParserTest { diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java index e9f2f75fab4..928999db776 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java @@ -1437,10 +1437,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(parameter.symbol().metadata().annotations().size()) - .isEqualTo(1); - assertThat(m.symbol().declarationParameters().get(0).metadata().annotations().size()) - .isEqualTo(1); + assertThat(parameter.symbol().metadata().annotations()) + .hasSize(1); + assertThat(m.symbol().declarationParameters().get(0).metadata().annotations()) + .hasSize(1); } @Test From 67336c9a73d2c7f7ee329f48bc1eaef6ac401293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 11 Nov 2026 16:07:02 +0100 Subject: [PATCH 8/8] Address Gitar review comment --- .../src/test/java/org/sonar/java/model/JSymbolTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java index 4ce55fbb152..96141a0e093 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java @@ -280,7 +280,6 @@ private void variable_in_class_initializer(boolean isStatic) { Symbol.TypeSymbol t = typeOf(c).symbol(); Symbol variableSymbol = v.symbol(); - assertThat(variableSymbol).isSameAs(v.symbol()); Symbol.MethodSymbol initializerBlock = (Symbol.MethodSymbol) variableSymbol.owner(); assertThat(variableSymbol).hasEnclosingClass(t);