All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sourcegraph.scip_semanticdb.SignatureFormatter Maven / Gradle / Ivy

package com.sourcegraph.scip_semanticdb;

import com.sourcegraph.semanticdb_javac.Semanticdb.SymbolInformation.Property;
import com.sourcegraph.semanticdb_javac.Semanticdb.*;

import com.sourcegraph.semanticdb_javac.SemanticdbSymbols;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.sourcegraph.semanticdb_javac.SemanticdbBuilders.typeRef;

public class SignatureFormatter {
  private static final Type OBJECT_TYPE_REF = typeRef("java/lang/Object#");
  private static final Type PRODUCT_TYPE_REF = typeRef("scala/Product#");
  private static final Type SCALA_SERIALIZABLE_TYPE_REF = typeRef("scala/package.Serializable#");
  private static final Type JAVA_SERIALIZABLE_TYPE_REF = typeRef("java/util/Serializable#");
  private static final Type SCALA_ANY_TYPE_REF = typeRef("scala/Any#");
  private static final Type SCALA_ANYREF_TYPE_REF = typeRef("scala/AnyRef#");

  private static final Type WILDCARD_TYPE_REF = typeRef("local_wildcard");

  private static final Type NOTHING_SYMBOL = typeRef("scala/Nothing#");
  private static final String FUNCTION_SYMBOL_PREFIX = "scala/Function";
  // Special case scala/Function object to not conflict with Function1 for example
  private static final String FUNCTION_OBJECT = "scala/Function.";
  private static final String TUPLE_SYMBOL_PREFIX = "scala/Tuple";
  private static final String ARRAY_SYMBOL = "scala/Array#";
  private static final String ENUM_SYMBOL = "java/lang/Enum#";
  private static final String ANNOTATION_SYMBOL = "java/lang/annotation/Annotation#";
  private static final Set REDUNDANT_CLASS_PARENTS = new HashSet<>();
  private static final Set CASE_CLASS_PARENTS = new HashSet<>();

  {
    REDUNDANT_CLASS_PARENTS.add(OBJECT_TYPE_REF);
    REDUNDANT_CLASS_PARENTS.add(SCALA_ANY_TYPE_REF);
    REDUNDANT_CLASS_PARENTS.add(SCALA_ANYREF_TYPE_REF);

    CASE_CLASS_PARENTS.add(PRODUCT_TYPE_REF);
    CASE_CLASS_PARENTS.add(SCALA_SERIALIZABLE_TYPE_REF);
    CASE_CLASS_PARENTS.add(JAVA_SERIALIZABLE_TYPE_REF);
  }

  private final StringBuilder s = new StringBuilder();
  private final SymbolInformation symbolInformation;
  private final Symtab symtab;
  private final boolean isScala;

  public SignatureFormatter(SymbolInformation symbolInformation, Symtab symtab) {
    this.symbolInformation = symbolInformation;
    this.symtab = symtab;
    this.isScala = symbolInformation.getLanguage() == Language.SCALA;
  }

  public String formatSymbol() {
    try {
      return formatSymbolUnsafe();
    } catch (Exception e) {
      throw new SignatureFormatterException(symbolInformation, e);
    }
  }

  private String formatSymbolUnsafe() {
    Signature signature = symbolInformation.getSignature();
    if (signature.hasClassSignature()) {
      formatClassSignature(signature.getClassSignature());
    } else if (signature.hasMethodSignature()) {
      formatMethodSignature(signature.getMethodSignature());
    } else if (signature.hasValueSignature()) {
      formatValueSignature(signature.getValueSignature());
    } else if (signature.hasTypeSignature()) {
      formatTypeParameterSignature(signature.getTypeSignature());
    }
    return s.toString();
  }

  private void formatClassSignature(ClassSignature classSignature) {
    boolean isAnnotation =
        classSignature.getParentsList().stream()
            .anyMatch(t -> t.getTypeRef().getSymbol().equals(ANNOTATION_SYMBOL));

    boolean isEnum = has(Property.ENUM);
    boolean isInterface = symbolInformation.getKind() == SymbolInformation.Kind.INTERFACE;

    printKeywordln(formatAnnotations());

    printKeyword(formatAccess());
    if (!isEnum && !isAnnotation && !isInterface) printKeyword(formatModifiers());

    switch (symbolInformation.getKind()) {
      case CLASS:
        if (isEnum) {
          printKeyword("enum");
        } else {
          printKeyword("class");
        }
        break;
      case INTERFACE:
        if (isAnnotation) {
          printKeyword("@interface");
          break;
        }
        printKeyword("interface");
        break;
      case OBJECT:
        printKeyword("object");
        break;
      case TRAIT:
        printKeyword("trait");
        break;
      case PACKAGE_OBJECT:
        printKeyword("package object");
        break;
      default:
        break;
    }
    s.append(symbolInformation.getDisplayName());
    if (symbolInformation.getKind() == SymbolInformation.Kind.CLASS && has(Property.CASE)) {
      primaryConstructor(classSignature)
          .ifPresent(
              constructorSignature ->
                  formatScalaParameterList(constructorSignature.getParameterListsList()));
    }

    List typeParameters = getSymlinks(classSignature.getTypeParameters());
    if (!typeParameters.isEmpty()) {
      s.append(
          typeParameters.stream()
              .map(this::formatTypeParameter)
              .collect(Collectors.joining(", ", isScala ? "[" : "<", isScala ? "]" : ">")));
    }

    boolean hasNonRedundantParent =
        classSignature.getParentsList().size() > 0
            && !REDUNDANT_CLASS_PARENTS.contains(classSignature.getParentsList().get(0));

    boolean isCaseClass =
        isScala
            && symbolInformation.getKind() == SymbolInformation.Kind.CLASS
            && has(Property.CASE);

    List nonSyntheticParents =
        classSignature.getParentsList().stream()
            .filter(parent -> !REDUNDANT_CLASS_PARENTS.contains(parent))
            .filter(parent -> !parent.getTypeRef().getSymbol().equals(ENUM_SYMBOL))
            .filter(parent -> isCaseClass && !CASE_CLASS_PARENTS.contains(parent))
            .filter(parent -> !parent.getTypeRef().getSymbol().equals(ANNOTATION_SYMBOL))
            .collect(Collectors.toList());

    if (nonSyntheticParents.isEmpty()) return;

    if (isScala) {
      printKeyword(" extends");
      s.append(
          nonSyntheticParents.stream().map(this::formatType).collect(Collectors.joining(" with ")));
      return;
    }

    // Determine which parents from ClassSignature.parents are classes or interfaces so we know to
    // use
    // 'extends' or 'implements'.
    // The logic is as follows:
    // 1. If the symbol has type CLASS, there will always be at least 1 parent. For enums, this is
    //    java/lang/Enum#, otherwise it is java/lang/Object# if no superclass is specified.
    //    Therefore, if the parents list contains java/lang/Object# type or the symbol is an enum,
    //    then no superclass was defined and all parents are interfaces and we must print
    // 'implements'
    //    followed by all superinterfaces.
    //    Else if it is not an enum and the list of non-synthetic parents is non empty, a superclass
    //    was specified and we must print it with the 'extends' keyword prefixed, followed by
    //    'implements' and all superinterfaces, if any.
    // 2. If the symbol has type INTERFACE, then any defined parents must also be interfaces, so if
    //    the list of non-synthetic parents is not empty, print 'implements' and all
    // superinterfaces.
    switch (symbolInformation.getKind()) {
      case CLASS:
        // if no superclass or is an enum, every non synthetic parent is an interface
        if (isEnum || !hasNonRedundantParent) {
          printKeyword(" implements");

          String superInterfaces =
              nonSyntheticParents.stream().map(this::formatType).collect(Collectors.joining(", "));
          s.append(superInterfaces);
        } else {
          // else if has a superclass and is not an enum
          printKeyword(" extends");
          s.append(formatType(nonSyntheticParents.get(0)));

          String superInterfaces =
              nonSyntheticParents.stream()
                  .skip(1)
                  .map(this::formatType)
                  .collect(Collectors.joining(", "));
          if (!superInterfaces.isEmpty()) {
            printKeyword(" implements");
            s.append(superInterfaces);
          }
        }
        break;
      case INTERFACE:
        // can only extend other interfaces
        printKeyword(" extends");

        String superInterfaces =
            nonSyntheticParents.stream().map(this::formatType).collect(Collectors.joining(", "));
        s.append(superInterfaces);
    }
  }

  private void formatMethodSignature(MethodSignature methodSignature) {
    if (isScala) {
      formatScalaMethodSignature(methodSignature);
      return;
    }

    printKeywordln(formatAnnotations());
    printKeyword(formatAccess());
    printKeyword(formatModifiers());

    List typeParameters = getSymlinks(methodSignature.getTypeParameters());
    if (!typeParameters.isEmpty()) {
      printKeyword(
          typeParameters.stream()
              .map(this::formatTypeParameter)
              .collect(Collectors.joining(", ", "<", ">")));
    }

    if (symbolInformation.getKind() == SymbolInformation.Kind.CONSTRUCTOR) {
      String owner = SymbolDescriptor.parseFromSymbol(symbolInformation.getSymbol()).owner;
      // Fix for https://github.com/sourcegraph/scip-java/issues/150
      if (!owner.equals(SemanticdbSymbols.NONE)) {
        s.append(SymbolDescriptor.parseFromSymbol(owner).descriptor.name);
      }
    } else {
      printKeyword(formatType(methodSignature.getReturnType()));
      s.append(symbolInformation.getDisplayName());
    }

    s.append(
        methodSignature.getParameterListsList().stream()
            .flatMap((params) -> getSymlinks(params).stream())
            .map(this::formatTermParameter)
            .collect(Collectors.joining(", ", "(", ")")));

    if (!methodSignature.getThrowsList().isEmpty()) {
      printKeyword(" throws");
      s.append(
          methodSignature.getThrowsList().stream()
              .map(this::formatType)
              .collect(Collectors.joining(", ")));
    }
  }

  private String formatTermParameter(SymbolInformation info) {
    if (info == null) return "";
    if (isScala) {
      return info.getDisplayName()
          + ": "
          + formatType(info.getSignature().getValueSignature().getTpe());
    }
    return formatType(info.getSignature().getValueSignature().getTpe())
        + " "
        + info.getDisplayName();
  }

  private void formatScalaMethodSignature(MethodSignature methodSignature) {
    printKeywordln(formatAnnotations());
    printKeyword(formatAccess());
    printKeyword(formatModifiers());
    if (has(Property.VAL)) {
      printKeyword("val");
    } else if (has(Property.VAR)) {
      printKeyword("var");
    } else {
      printKeyword("def");
    }
    s.append(
        symbolInformation.getKind() == SymbolInformation.Kind.CONSTRUCTOR
            ? "this"
            : symbolInformation.getDisplayName());
    formatScalaParameterList(methodSignature.getParameterListsList());
    if (symbolInformation.getKind() != SymbolInformation.Kind.CONSTRUCTOR) {
      printKeyword(":");
      s.append(this.formatType(methodSignature.getReturnType()));
    }
  }

  private Optional primaryConstructor(ClassSignature classSignature) {
    Symtab scopeSymtab = symtab.withHardlinks(classSignature.getDeclarations());
    int n = classSignature.getDeclarations().getSymlinksCount();
    for (int i = 0; i < n; i++) {
      String symlink = classSignature.getDeclarations().getSymlinks(i);
      SymbolInformation info = scopeSymtab.symbols.get(symlink);
      if (info != null
          && info.getKind() == SymbolInformation.Kind.CONSTRUCTOR
          && has(Property.PRIMARY, info)
          && info.hasSignature()
          && info.getSignature().hasMethodSignature()) {
        return Optional.of(info.getSignature().getMethodSignature());
      }
    }
    return Optional.empty();
  }

  private void formatScalaParameterList(List parameterList) {
    for (Scope scope : parameterList) {
      List infos =
          scope.getHardlinksCount() > 0 ? scope.getHardlinksList() : getSymlinks(scope);
      s.append(
          infos.stream()
              .map(this::formatTermParameter)
              .collect(Collectors.joining(", ", "(", ")")));
    }
  }

  private void formatValueSignature(ValueSignature valueSignature) {
    printKeywordln(formatAnnotations());
    if (isEnumConstant()) {
      String ownerSym = SymbolDescriptor.parseFromSymbol(symbolInformation.getSymbol()).owner;
      SymbolInformation ownerInfo = symtab.symbols.get(ownerSym);
      List enumConstants =
          getSymlinks(ownerInfo.getSignature().getClassSignature().getDeclarations()).stream()
              .filter(Objects::nonNull)
              .filter(this::isEnumConstant)
              .collect(Collectors.toList());
      int ordinal = enumConstants.indexOf(symbolInformation);
      s.append(ownerInfo.getDisplayName()).append('.');
      s.append(this.symbolInformation.getDisplayName());
      s.append(" /* ordinal ").append(ordinal).append(" */");
    } else {
      printKeyword(formatAccess());
      printKeyword(formatModifiers());
      if (isScala) {
        s.append(symbolInformation.getDisplayName());
        printKeyword(":");
        printKeyword(formatType(valueSignature.getTpe()));
      } else {
        printKeyword(formatType(valueSignature.getTpe()));
        s.append(symbolInformation.getDisplayName());
      }
    }
  }

  private void formatTypeParameterSignature(TypeSignature typeSignature) {
    if (isScala && symbolInformation.getKind() == SymbolInformation.Kind.TYPE) {
      printKeyword("type");
    }
    s.append(symbolInformation.getDisplayName());
    if (typeSignature.hasLowerBound()
        && (!isScala || !typeSignature.getLowerBound().equals(NOTHING_SYMBOL))) {
      printKeyword(isScala ? " >:" : " super");
      s.append(formatType(typeSignature.getLowerBound()));
    }
    if (typeSignature.hasUpperBound()
        && !typeSignature.getUpperBound().equals(isScala ? SCALA_ANY_TYPE_REF : OBJECT_TYPE_REF)) {
      printKeyword(isScala ? " <:" : " extends");
      s.append(formatType(typeSignature.getUpperBound()));
    }
  }

  /**
   * Transforms symlinks from a Scope into a List of SymbolInformation's looked up in the Symtab.
   */
  private List getSymlinks(Scope scope) {
    ArrayList symlinks = new ArrayList<>();
    for (int i = 0; i < scope.getSymlinksCount(); i++) {
      SymbolInformation info = symtab.symbols.get(scope.getSymlinks(i));
      if (info != null) {
        symlinks.add(info);
      }
    }
    return symlinks;
  }

  /**
   * Formats one of a method's/class's type parameter symbols through recursion from the
   * SymbolInformation extracted from the Symtab. This works by the signature being a TypeSignature.
   */
  private String formatTypeParameter(SymbolInformation typeInfo) {
    return new SignatureFormatter(typeInfo, symtab).formatSymbol();
  }

  private String formatTypeArguments(List typeArguments) {
    if (typeArguments.isEmpty()) return "";

    return typeArguments.stream()
        .map(this::formatType)
        .collect(Collectors.joining(", ", isScala ? "[" : "<", isScala ? "]" : ">"));
  }

  private String formatAnnotations() {
    return formatAnnotations(symbolInformation);
  }

  private String formatAnnotations(SymbolInformation symInfo) {
    return symInfo.getAnnotationsList().stream()
        .map(this::formatAnnotation)
        .collect(Collectors.joining("\n"));
  }

  private String formatAnnotation(AnnotationTree annotation) {
    StringBuilder b = new StringBuilder();
    b.append('@');
    b.append(formatType(annotation.getTpe()));

    if (annotation.getParametersCount() == 1) {
      b.append('(');

      Tree parameter = annotation.getParameters(0);
      // if only 1 parameter, and its LHS is named 'value', we can omit the 'value = '
      // https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7.3
      AssignTree firstParam = parameter.getAssignTree();
      if (parameter.hasAssignTree()
          && SymbolDescriptor.parseFromSymbol(firstParam.getLhs().getIdTree().getSymbol())
              .descriptor
              .name
              .equals("value")) {
        b.append(formatTree(firstParam.getRhs()));
      } else {
        b.append(formatTree(parameter));
      }

      b.append(')');
    } else if (annotation.getParametersCount() > 1) {
      b.append('(');
      String parameter =
          annotation.getParametersList().stream()
              .map(this::formatTree)
              .collect(Collectors.joining(", "));
      b.append(parameter);
      b.append(')');
    }

    return b.toString();
  }

  private String formatTree(Tree tree) {
    if (tree.hasIdTree()) {
      return SymbolDescriptor.parseFromSymbol(tree.getIdTree().getSymbol()).descriptor.name;
    } else if (tree.hasLiteralTree()) {
      return formatConstant(tree.getLiteralTree().getConstant());
    } else if (tree.hasSelectTree()) {
      return formatTree(tree.getSelectTree().getQualifier())
          + "."
          + SymbolDescriptor.parseFromSymbol(tree.getSelectTree().getId().getSymbol())
              .descriptor
              .name;
    } else if (tree.hasAnnotationTree()) {
      return formatAnnotation(tree.getAnnotationTree());
    } else if (tree.hasApplyTree()) {
      if (tree.getApplyTree().getFunction().hasIdTree()
          && tree.getApplyTree().getFunction().getIdTree().getSymbol().equals(ARRAY_SYMBOL)) {
        return tree.getApplyTree().getArgumentsList().stream()
            .map(this::formatTree)
            .collect(Collectors.joining(", ", "{", "}"));
      } else {
        throw new IllegalArgumentException(
            "unexpected apply tree function " + tree.getApplyTree().getFunction());
      }
    } else if (tree.hasBinopTree()) {
      return formatTree(tree.getBinopTree().getLhs())
          + " "
          + formatBinaryOperator(tree.getBinopTree().getOp())
          + " "
          + formatTree(tree.getBinopTree().getRhs());
    } else if (tree.hasAssignTree()) {
      return formatTree(tree.getAssignTree().getLhs())
          + " = "
          + formatTree(tree.getAssignTree().getRhs());
    }

    throw new IllegalArgumentException("tree was of unexpected type " + tree);
  }

  private String formatConstant(Constant constant) {
    if (constant.hasUnitConstant()) {
      return isScala ? "()" : "scala.Unit()";
    } else if (constant.hasBooleanConstant()) {
      return Boolean.toString(constant.getBooleanConstant().getValue());
    } else if (constant.hasByteConstant()) {
      return Integer.toString(constant.getByteConstant().getValue());
    } else if (constant.hasShortConstant()) {
      return Integer.toString(constant.getShortConstant().getValue());
    } else if (constant.hasCharConstant()) {
      return String.format("'%s'", (char) constant.getCharConstant().getValue());
    } else if (constant.hasIntConstant()) {
      return Integer.toString(constant.getIntConstant().getValue());
    } else if (constant.hasLongConstant()) {
      return Long.toString(constant.getLongConstant().getValue());
    } else if (constant.hasFloatConstant()) {
      return Float.toString(constant.getFloatConstant().getValue()) + 'f';
    } else if (constant.hasDoubleConstant()) {
      return Double.toString(constant.getDoubleConstant().getValue());
    } else if (constant.hasStringConstant()) {
      return '"' + constant.getStringConstant().getValue() + '"';
    } else if (constant.hasNullConstant()) {
      return "null";
    }
    throw new IllegalArgumentException("constant was not of known type " + constant);
  }

  private String formatBinaryOperator(BinaryOperator op) {
    switch (op) {
      case PLUS:
        return "+";
      case MINUS:
        return "-";
      case MULTIPLY:
        return "*";
      case DIVIDE:
        return "/";
      case REMAINDER:
        return "%";
      case GREATER_THAN:
        return ">";
      case LESS_THAN:
        return "<";
      case AND:
        return "&";
      case XOR:
        return "^";
      case OR:
        return "|";
      case CONDITIONAL_AND:
        return "&&";
      case CONDITIONAL_OR:
        return "||";
      case SHIFT_LEFT:
        return "<<";
      case SHIFT_RIGHT:
        return ">>";
      case SHIFT_RIGHT_UNSIGNED:
        return ">>>";
      case EQUAL_TO:
        return "==";
      case NOT_EQUAL_TO:
        return "!=";
      case GREATER_THAN_EQUAL:
        return ">=";
      case LESS_THAN_EQUAL:
        return "<=";
      case UNRECOGNIZED:
        throw new IllegalArgumentException("unexpected binary operator " + op);
    }
    return null;
  }

  private String formatType(Type type) {
    StringBuilder b = new StringBuilder();
    if (type.hasTypeRef()) {
      TypeRef typeRef = type.getTypeRef();
      if (typeRef.getSymbol().equals(ARRAY_SYMBOL)) {
        if (isScala) {
          b.append("Array[");
          b.append(formatType(typeRef.getTypeArguments(0)));
          b.append("]");
        } else {
          b.append(formatType(typeRef.getTypeArguments(0)));
          b.append("[]");
        }
      } else if (isScala
          && typeRef.getSymbol().startsWith(FUNCTION_SYMBOL_PREFIX)
          && typeRef.getTypeArgumentsCount() > 0
          && !typeRef.getSymbol().startsWith(FUNCTION_OBJECT)) {
        int n = typeRef.getTypeArgumentsCount() - 1;
        if (n == 0) {
          // Special-case for Function1[A, B]: don't wrap `A`  in parenthesis like this `(A) => B`
          s.append(formatType(typeRef.getTypeArguments(0)));
        } else {
          b.append(
              typeRef.getTypeArgumentsList().stream()
                  .limit(Math.max(0, n))
                  .map(this::formatType)
                  .collect(Collectors.joining(", ", "(", ")")));
        }
        b.append(" => ");
        b.append(formatType(typeRef.getTypeArguments(n)));
      } else if (isScala && typeRef.getSymbol().startsWith(TUPLE_SYMBOL_PREFIX)) {
        b.append(
            typeRef.getTypeArgumentsList().stream()
                .map(this::formatType)
                .collect(Collectors.joining(", ", "(", ")")));
      } else {
        b.append(symbolDisplayName(typeRef.getSymbol()));
        b.append(formatTypeArguments(typeRef.getTypeArgumentsList()));
      }
    } else if (type.hasSingleType()) {
      SingleType tpe = type.getSingleType();
      if (tpe.hasPrefix()) {
        b.append(formatType(tpe.getPrefix()));
      }
      SymbolInformation info = symtab.symbols.get(tpe.getSymbol());
      if (info != null) {
        b.append(info.getDisplayName()).append(".type");
      }
    } else if (type.hasThisType()) {
      b.append("this.type");
    } else if (type.hasSuperType()) {
      SuperType tpe = type.getSuperType();
      if (tpe.hasPrefix()) {
        b.append(formatType(tpe.getPrefix())).append(".");
      }
      b.append("super");
    } else if (type.hasConstantType()) {
      b.append(this.formatConstant(type.getConstantType().getConstant()));
    } else if (type.hasIntersectionType()) {
      b.append(
          type.getIntersectionType().getTypesList().stream()
              .map(this::formatType)
              .collect(Collectors.joining(isScala ? " with " : " & ")));
    } else if (type.hasUnionType()) {
      b.append(
          type.getIntersectionType().getTypesList().stream()
              .map(this::formatType)
              .collect(Collectors.joining(" | ")));
    } else if (type.hasStructuralType()) {
      StructuralType tpe = type.getStructuralType();
      int n = tpe.getDeclarations().getHardlinksCount();
      if (n == 0) {
        b.append(" {}");
      } else {
        b.append(formatType(tpe.getTpe())).append(" {");
        Symtab hardlinkSymtab = symtab.withHardlinks(tpe.getDeclarations());
        for (int i = 0; i < n; i++) {
          SymbolInformation info = tpe.getDeclarations().getHardlinks(i);
          if (i > 0) {
            b.append(";");
          }
          b.append(" ").append(new SignatureFormatter(info, hardlinkSymtab).formatSymbol());
        }
        b.append(" }");
      }
    } else if (type.hasExistentialType()) {
      AtomicInteger hardlinkStep = new AtomicInteger();
      TypeRef typeRef = type.getExistentialType().getTpe().getTypeRef();
      b.append(symbolDisplayName(type.getExistentialType().getTpe().getTypeRef().getSymbol()));
      b.append(
          typeRef.getTypeArgumentsList().stream()
              .map(
                  (typeArg) -> {
                    // if hardlink (aka wildcard) we need to reach into declarations at index
                    // hardlinkStep
                    if (typeArg.equals(WILDCARD_TYPE_REF)) {
                      SymbolInformation hardlink =
                          type.getExistentialType()
                              .getDeclarations()
                              .getHardlinks(hardlinkStep.getAndIncrement());
                      return symbolDisplayName(hardlink.getSymbol())
                          + new SignatureFormatter(hardlink, symtab).formatSymbol();
                    }
                    // else for symlink we can use the usual path
                    return formatType(typeArg);
                  })
              .collect(Collectors.joining(", ", isScala ? "[" : "<", isScala ? "[" : ">")));
    } else if (type.hasByNameType()) {
      b.append("=> ").append(formatType(type.getByNameType().getTpe()));
    } else if (type.hasRepeatedType()) {
      b.append(formatType(type.getRepeatedType().getTpe())).append("*");
    }

    return b.toString().trim();
  }

  private String formatAccess() {
    Access access = symbolInformation.getAccess();
    if (access.hasPrivateAccess()) {
      return "private";
    } else if (!isScala && access.hasPublicAccess()) {
      return "public";
    } else if (access.hasProtectedAccess()) {
      return "protected";
    } else if (isScala && access.hasPrivateThisAccess()) {
      return "private[this]";
    } else if (isScala && access.hasPrivateWithinAccess()) {
      String name =
          SymbolDescriptor.parseFromSymbol(access.getPrivateWithinAccess().getSymbol())
              .descriptor
              .name;
      return String.format("protected[%s]", name);
    }
    return "";
  }

  // https://checkstyle.sourceforge.io/config_modifier.html#ModifierOrder
  private String formatModifiers() {
    ArrayList modifiers = new ArrayList<>();
    if (has(Property.ABSTRACT)) {
      if (isScala && symbolInformation.getKind() != SymbolInformation.Kind.CLASS) {
      } else {
        modifiers.add("abstract");
      }
    }
    if (has(Property.DEFAULT)) modifiers.add("default");
    if (has(Property.STATIC)) modifiers.add("static");
    if (has(Property.FINAL)) {
      if (symbolInformation.getKind() != SymbolInformation.Kind.OBJECT
          && symbolInformation.getKind() != SymbolInformation.Kind.PACKAGE_OBJECT) {
        modifiers.add("final");
      }
    }
    if (has(Property.IMPLICIT)) modifiers.add("implicit");
    if (has(Property.SEALED)) modifiers.add("sealed");
    if (has(Property.CASE)) modifiers.add("case");
    return String.join(" ", modifiers);
  }

  private void printKeyword(String keyword) {
    if (keyword.isEmpty()) return;
    s.append(keyword).append(' ');
  }

  private void printKeywordln(String keyword) {
    if (keyword.isEmpty()) return;
    s.append(keyword).append('\n');
  }

  private boolean isEnumConstant(SymbolInformation symInfo) {
    if (!(has(Property.ENUM, symInfo)
        && has(Property.FINAL, symInfo)
        && has(Property.STATIC, symInfo)
        && symInfo.getAccess().hasPublicAccess())) {
      return false;
    }
    SymbolInformation owner =
        symtab.symbols.get(SymbolDescriptor.parseFromSymbol(symInfo.getSymbol()).owner);
    if (owner == null) return false;
    return owner.getKind() == SymbolInformation.Kind.CLASS && has(Property.ENUM, owner);
  }

  private boolean isEnumConstant() {
    return isEnumConstant(symbolInformation);
  }

  private boolean has(Property property, SymbolInformation symInfo) {
    return (symInfo.getProperties() & property.getNumber()) > 0;
  }

  private boolean has(Property property) {
    return has(property, symbolInformation);
  }

  /**
   * Transforms a SemanticDB symbol string into its Java identifier display string. As SemanticDB
   * uses Scala "primitive" types for Java primitives (but not for Java boxing primitive wrappers),
   * we check for those first before attempting to decode a SemanticDB symbol.
   */
  public String symbolDisplayName(String symbol) {
    if (isScala) {
      return symbolScalaDisplayName(symbol);
    }
    return symbolJavaDisplayName(symbol);
  }

  private String symbolScalaDisplayName(String symbol) {
    if ("local_wildcard".equals(symbol)) {
      return "*";
    }
    return SymbolDescriptor.parseFromSymbol(symbol).descriptor.name;
  }

  private String symbolJavaDisplayName(String symbol) {
    switch (symbol) {
      case "local_wildcard":
        return "?";
      case "scala/Boolean#":
        return "boolean";
      case "scala/Byte#":
        return "byte";
      case "scala/Short#":
        return "short";
      case "scala/Int#":
        return "int";
      case "scala/Long#":
        return "long";
      case "scala/Char#":
        return "char";
      case "scala/Float#":
        return "float";
      case "scala/Double#":
        return "double";
      case "scala/Unit#":
        return "void";
      default:
        return SymbolDescriptor.parseFromSymbol(symbol).descriptor.name;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy