Repository navigation
SONARJAVA-7283 Decouple the frontend AST from Eclipse JDT core #6449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d4b1f8c
ab39529
bf7fd58
bf825e5
2d2821f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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.model; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.Set; | ||
| import java.util.stream.Stream; | ||
| import org.eclipse.jdt.core.JavaCore; | ||
| import org.eclipse.jdt.core.compiler.IProblem; | ||
| import org.eclipse.jdt.core.dom.CompilationUnit; | ||
|
|
||
| final class EcjDiagnosticProvider { | ||
|
|
||
| private static final Map<JProblem.Type, Integer> IDS = Map.of( | ||
| JProblem.Type.UNDEFINED_TYPE, IProblem.UndefinedType, | ||
| JProblem.Type.PREVIEW_FEATURE_USED, IProblem.PreviewFeatureUsed, | ||
| JProblem.Type.UNUSED_IMPORT, IProblem.UnusedImport, | ||
| JProblem.Type.REDUNDANT_CAST, IProblem.UnnecessaryCast, | ||
| JProblem.Type.ASSIGNMENT_HAS_NO_EFFECT, IProblem.AssignmentHasNoEffect, | ||
| JProblem.Type.MASKED_CATCH, IProblem.MaskedCatch); | ||
|
|
||
| private EcjDiagnosticProvider() { | ||
| } | ||
|
|
||
| static Set<String> compilerOptions() { | ||
| return Set.of( | ||
| JavaCore.COMPILER_PB_UNUSED_IMPORT, | ||
| JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK, | ||
| JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT, | ||
| JavaCore.COMPILER_PB_HIDDEN_CATCH_BLOCK | ||
| ); | ||
| } | ||
|
|
||
| static List<JWarning> warnings(CompilationUnit ast, LineColumnConverter positions) { | ||
| return Stream.of(ast.getProblems()) | ||
| .map(problem -> convert(problem, positions)) | ||
| .filter(Objects::nonNull) | ||
| .toList(); | ||
| } | ||
|
|
||
| private static JWarning convert(IProblem problem, LineColumnConverter positions) { | ||
| for (JProblem.Type type : JProblem.Type.values()) { | ||
| if (problem.getID() == IDS.get(type)) { | ||
| LineColumnConverter.Pos start = positions.toPos(problem.getSourceStart()); | ||
| LineColumnConverter.Pos end = positions.toPos(problem.getSourceEnd()); | ||
| return new JWarning(problem.getMessage(), type, start.line(), start.columnOffset(), end.line(), end.columnOffset()); | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -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.model; | ||||||
|
|
||||||
| import java.util.Objects; | ||||||
| import java.util.function.Function; | ||||||
| import java.util.function.Supplier; | ||||||
| import javax.annotation.Nullable; | ||||||
| import org.eclipse.jdt.core.dom.IBinding; | ||||||
|
|
||||||
| final class EcjSemanticAttachment<B extends IBinding, T> implements Supplier<T> { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If i'm correct we can constrain
Suggested change
|
||||||
|
|
||||||
| final B binding; | ||||||
| private final Function<B, T> factory; | ||||||
| private T value; | ||||||
|
|
||||||
| EcjSemanticAttachment(B binding, Function<B, T> factory) { | ||||||
| this.binding = binding; | ||||||
| this.factory = factory; | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public T get() { | ||||||
| if (value == null) { | ||||||
| value = Objects.requireNonNull(factory.apply(binding)); | ||||||
| } | ||||||
| return value; | ||||||
| } | ||||||
|
|
||||||
| @Nullable | ||||||
| static IBinding getBinding(Supplier<?> attachment) { | ||||||
| return attachment instanceof EcjSemanticAttachment<?, ?> ecj ? ecj.binding : null; | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| /* | ||
| * 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.List; | ||
| import java.util.stream.Collectors; | ||
| import org.eclipse.jdt.core.dom.IAnnotationBinding; | ||
| import org.eclipse.jdt.core.dom.ITypeBinding; | ||
| import org.eclipse.jdt.core.dom.IVariableBinding; | ||
| import org.sonar.plugins.java.api.semantic.Symbol; | ||
| import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationInstance; | ||
| import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationValue; | ||
|
|
||
| final class EcjSymbolMetadata { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class has no superclass, as the goal of this effort is to wrap everything behind generic interfaces/abstract classes/api not ecj-aware, this class should extend a generic one, and this raw type should never be used except for construction (can be done in another PR) |
||
|
|
||
| private EcjSymbolMetadata() { | ||
| } | ||
|
|
||
| static JSymbolMetadata declaration(JSema sema, Symbol owner, IAnnotationBinding[] bindings) { | ||
| return metadata(sema, owner, bindings, new JSymbolMetadata[0], false); | ||
| } | ||
|
|
||
| static JSymbolMetadata of(JSema sema, Symbol owner, ITypeBinding type, IAnnotationBinding[] externalAnnotations) { | ||
| var symbolAnnotations = new IAnnotationBinding[externalAnnotations.length + type.getTypeAnnotations().length]; | ||
| System.arraycopy(externalAnnotations, 0, symbolAnnotations, 0, externalAnnotations.length); | ||
| System.arraycopy(type.getTypeAnnotations(), 0, symbolAnnotations, externalAnnotations.length, type.getTypeAnnotations().length); | ||
|
|
||
| var parameterMetaDatas = Arrays.stream(type.getTypeArguments()) | ||
| .map(param -> ofTypeArg(sema, owner, param)) | ||
| .toArray(JSymbolMetadata[]::new); | ||
|
|
||
| return metadata(sema, owner, symbolAnnotations, parameterMetaDatas, false); | ||
| } | ||
|
|
||
| private static JSymbolMetadata ofTypeArg(JSema sema, Symbol owner, ITypeBinding param) { | ||
| var symbolAnnotations = param.getTypeAnnotations(); | ||
| var parameterMetaDatas = Arrays.stream(param.getTypeArguments()) | ||
| .map(p -> ofTypeArg(sema, owner, p)) | ||
| .toArray(JSymbolMetadata[]::new); | ||
| return metadata(sema, owner, symbolAnnotations, parameterMetaDatas, true); | ||
| } | ||
|
|
||
| private static JSymbolMetadata metadata(JSema sema, Symbol owner, IAnnotationBinding[] bindings, JSymbolMetadata[] parameters, boolean typeArgument) { | ||
| return new JSymbolMetadata(owner, () -> Arrays.stream(bindings).map(sema::annotation).collect(Collectors.toList()), parameters, typeArgument); | ||
| } | ||
|
|
||
| static final class JAnnotationInstance implements AnnotationInstance { | ||
| private final JSema sema; | ||
| private final IAnnotationBinding annotationBinding; | ||
|
|
||
| /** | ||
| * Cache for {@link #values()}. | ||
| */ | ||
| private List<AnnotationValue> values; | ||
|
|
||
| JAnnotationInstance(JSema sema, IAnnotationBinding annotationBinding) { | ||
| this.sema = sema; | ||
| this.annotationBinding = annotationBinding; | ||
| } | ||
|
|
||
| @Override | ||
| public Symbol symbol() { | ||
| return sema.typeSymbol(annotationBinding.getAnnotationType()); | ||
| } | ||
|
|
||
| @Override | ||
| public List<AnnotationValue> values() { | ||
| if (values == null) { | ||
| values = Arrays.stream(annotationBinding.getDeclaredMemberValuePairs()) | ||
| .map(p -> new AnnotationValueImpl(p.getName(), convertAnnotationValue(p.getValue()))) | ||
| .collect(Collectors.toList()); | ||
| } | ||
| return values; | ||
| } | ||
|
|
||
| private Object convertAnnotationValue(Object value) { | ||
| return EcjSymbolMetadata.convertAnnotationValue(sema, value); | ||
| } | ||
| } | ||
|
|
||
| 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]); | ||
| } | ||
| yield result; | ||
| } | ||
| case null, default -> value; | ||
| }; | ||
| } | ||
| } | ||
|
Comment on lines
+91
to
+111
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These 2 return types are too broad to me, the return type could be symbol to be bounded and easier to understand |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| /* | ||
| * 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.function.Supplier; | ||
| import javax.annotation.Nullable; | ||
| import org.eclipse.jdt.core.dom.IBinding; | ||
| import org.eclipse.jdt.core.dom.IMethodBinding; | ||
| import org.eclipse.jdt.core.dom.ITypeBinding; | ||
| import org.eclipse.jdt.core.dom.IVariableBinding; | ||
| import org.sonar.java.model.declaration.ClassTreeImpl; | ||
| import org.sonar.java.model.declaration.MethodTreeImpl; | ||
| import org.sonar.java.model.declaration.VariableTreeImpl; | ||
| import org.sonar.java.model.expression.IdentifierTreeImpl; | ||
| import org.sonar.java.model.expression.LambdaExpressionTreeImpl; | ||
| import org.sonar.java.model.expression.MethodInvocationTreeImpl; | ||
| import org.sonar.java.model.pattern.AbstractPatternTree; | ||
|
|
||
| final class EcjTreeBindings { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for this one |
||
|
|
||
| private EcjTreeBindings() { | ||
| } | ||
|
|
||
| @Nullable | ||
| static ITypeBinding typeBinding(Object tree) { | ||
| return switch (tree) { | ||
| case AbstractTypedTree t -> (ITypeBinding) EcjSemanticAttachment.getBinding(t.type); | ||
| case ClassTreeImpl t -> (ITypeBinding) EcjSemanticAttachment.getBinding(t.typeSymbol); | ||
| case TypeParameterTreeImpl t -> (ITypeBinding) EcjSemanticAttachment.getBinding(t.typeSymbol); | ||
| case AbstractPatternTree t -> (ITypeBinding) EcjSemanticAttachment.getBinding(t.type); | ||
| default -> throw new IllegalArgumentException("No ECJ type attachment: " + tree.getClass()); | ||
| }; | ||
| } | ||
|
|
||
| @Nullable | ||
| static IMethodBinding methodBinding(Object tree) { | ||
| Supplier<?> attachment = switch (tree) { | ||
| case MethodTreeImpl t -> t.methodSymbol; | ||
| case MethodInvocationTreeImpl t -> t.methodSymbolSupplier; | ||
| case LambdaExpressionTreeImpl t -> t.methodSymbol; | ||
| default -> throw new IllegalArgumentException("No ECJ method attachment: " + tree.getClass()); | ||
| }; | ||
| return (IMethodBinding) EcjSemanticAttachment.getBinding(attachment); | ||
| } | ||
|
|
||
| @Nullable | ||
| static IVariableBinding variableBinding(VariableTreeImpl tree) { | ||
| return (IVariableBinding) EcjSemanticAttachment.getBinding(tree.variableSymbol); | ||
| } | ||
|
|
||
| @Nullable | ||
| static IBinding binding(Object tree) { | ||
| return switch (tree) { | ||
| case IdentifierTreeImpl t -> EcjSemanticAttachment.getBinding(t.symbolSupplier); | ||
| case JavaTree.ImportTreeImpl t -> EcjSemanticAttachment.getBinding(t.symbolSupplier); | ||
| default -> throw new IllegalArgumentException("No ECJ symbol attachment: " + tree.getClass()); | ||
| }; | ||
| } | ||
|
|
||
| static void setType(JSema sema, JavaTree tree, @Nullable ITypeBinding binding) { | ||
| switch (tree) { | ||
| case AbstractTypedTree t -> t.type = sema.typeSupplier(binding); | ||
| case ClassTreeImpl t -> t.typeSymbol = binding == null ? SemanticValues.UNKNOWN_TYPE_SYMBOL : new EcjSemanticAttachment<>(binding, sema::typeSymbol); | ||
| case TypeParameterTreeImpl t -> t.typeSymbol = sema.symbolSupplier(binding); | ||
| default -> throw new IllegalArgumentException("No type attachment: " + tree.getClass()); | ||
| } | ||
| if (tree instanceof KeywordThis t) { | ||
| t.symbolSupplier = binding == null ? SemanticValues.UNKNOWN_SYMBOL : new EcjSemanticAttachment<>(binding, b -> sema.typeSymbol(b).thisSymbol); | ||
| } else if (tree instanceof KeywordSuper t) { | ||
| t.symbolSupplier = binding == null ? SemanticValues.UNKNOWN_SYMBOL : new EcjSemanticAttachment<>(binding, b -> sema.typeSymbol(b).superSymbol); | ||
| } | ||
| } | ||
|
|
||
| static void setSymbol(JSema sema, JavaTree tree, @Nullable IBinding binding) { | ||
| switch (tree) { | ||
| case MethodTreeImpl t -> t.methodSymbol = sema.methodSupplier((IMethodBinding) binding); | ||
| case MethodInvocationTreeImpl t -> t.methodSymbolSupplier = sema.methodSupplier((IMethodBinding) binding); | ||
| case LambdaExpressionTreeImpl t -> t.methodSymbol = sema.methodSupplier((IMethodBinding) binding); | ||
| case VariableTreeImpl t -> t.variableSymbol = sema.symbolSupplier(binding); | ||
| case IdentifierTreeImpl t -> t.symbolSupplier = sema.symbolSupplier(binding); | ||
| case JavaTree.ImportTreeImpl t -> t.symbolSupplier = (binding == null || binding.getKind() == IBinding.PACKAGE || binding.getKind() == IBinding.MODULE) | ||
| ? () -> null | ||
| : sema.symbolSupplier(binding); | ||
| default -> throw new IllegalArgumentException("No symbol attachment: " + tree.getClass()); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,12 @@ IMethodBinding methodBinding() { | |
| return (IMethodBinding) binding; | ||
| } | ||
|
|
||
| @Override | ||
| @Nullable | ||
| public Object defaultValue() { | ||
| return EcjSymbolMetadata.convertAnnotationValue(sema, methodBinding().getDefaultValue()); | ||
| } | ||
|
Comment on lines
+73
to
+77
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the return type could also be bounded to Symbol |
||
|
|
||
| public boolean isLambda() { | ||
| return methodBinding().getDeclaringMember() != null; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for this onex