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

org.sonar.python.semantic.FunctionSymbolImpl Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Python Plugin
 * Copyright (C) 2011-2024 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * 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 GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.python.semantic;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.LocationInFile;
import org.sonar.plugins.python.api.PythonFile;
import org.sonar.plugins.python.api.symbols.FunctionSymbol;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.AnyParameter;
import org.sonar.plugins.python.api.tree.Decorator;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.ParameterList;
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.TypeAnnotation;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.python.index.FunctionDescriptor;
import org.sonar.python.tree.TreeUtils;
import org.sonar.python.types.InferredTypes;
import org.sonar.python.types.TypeShed;
import org.sonar.python.types.protobuf.SymbolsProtos;

import static org.sonar.python.semantic.SymbolUtils.pathOf;
import static org.sonar.python.tree.TreeUtils.locationInFile;
import static org.sonar.python.types.InferredTypes.anyType;
import static org.sonar.python.types.InferredTypes.fromTypeAnnotation;
import static org.sonar.python.types.InferredTypes.fromTypeshedTypeAnnotation;

public class FunctionSymbolImpl extends SymbolImpl implements FunctionSymbol {
  private final List parameters = new ArrayList<>();
  private final List decorators;
  private final LocationInFile functionDefinitionLocation;
  private boolean hasVariadicParameter = false;
  private final boolean isInstanceMethod;
  private final boolean isAsynchronous;
  private final boolean hasDecorators;
  private String annotatedReturnTypeName = null;
  private SymbolsProtos.Type protobufReturnType = null;
  private InferredType declaredReturnType = InferredTypes.anyType();
  private final boolean isStub;
  private Symbol owner;
  private static final String CLASS_METHOD_DECORATOR = "classmethod";
  private static final String STATIC_METHOD_DECORATOR = "staticmethod";
  private boolean isDjangoView = false;
  private boolean hasReadDeclaredReturnType = false;

  FunctionSymbolImpl(FunctionDef functionDef, @Nullable String fullyQualifiedName, PythonFile pythonFile) {
    super(functionDef.name().name(), fullyQualifiedName);
    setKind(Kind.FUNCTION);
    isInstanceMethod = isInstanceMethod(functionDef);
    isAsynchronous = functionDef.asyncKeyword() != null;
    hasDecorators = !functionDef.decorators().isEmpty();
    decorators = decorators(functionDef);
    isStub = false;
    String fileId = Optional.ofNullable(pathOf(pythonFile)).map(Path::toString).orElse(pythonFile.toString());
    functionDefinitionLocation = locationInFile(functionDef.name(), fileId);
  }

  public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, String moduleName) {
    this(functionSymbolProto, null, functionSymbolProto.getValidForList(), moduleName);
  }

  public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, @Nullable String containerClassFqn, String moduleName) {
    this(functionSymbolProto, containerClassFqn, functionSymbolProto.getValidForList(), moduleName);
  }

  public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, @Nullable String containerClassFqn, List validFor, String moduleName) {
    super(functionSymbolProto.getName(), TypeShed.normalizedFqn(functionSymbolProto.getFullyQualifiedName(), moduleName, functionSymbolProto.getName(), containerClassFqn));
    setKind(Kind.FUNCTION);
    isInstanceMethod = containerClassFqn != null && !functionSymbolProto.getIsStatic() && !functionSymbolProto.getIsClassMethod();
    isAsynchronous = functionSymbolProto.getIsAsynchronous();
    hasDecorators = functionSymbolProto.getHasDecorators();
    decorators = functionSymbolProto.getResolvedDecoratorNamesList();
    SymbolsProtos.Type returnAnnotation = functionSymbolProto.getReturnAnnotation();
    String returnTypeName = returnAnnotation.getFullyQualifiedName();
    annotatedReturnTypeName = returnTypeName.isEmpty() ? null : TypeShed.normalizedFqn(returnTypeName);
    protobufReturnType = returnAnnotation;
    for (SymbolsProtos.ParameterSymbol parameterSymbol : functionSymbolProto.getParametersList()) {
      ParameterState parameterState = new ParameterState();
      parameterState.positionalOnly = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.POSITIONAL_ONLY;
      parameterState.keywordOnly = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.KEYWORD_ONLY;
      boolean isKeywordVariadic = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.VAR_KEYWORD;
      boolean isPositionalVariadic = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.VAR_POSITIONAL;
      hasVariadicParameter |= isKeywordVariadic || isPositionalVariadic;
      InferredType declaredType;
      if (isPositionalVariadic) {
        declaredType = InferredTypes.TUPLE;
      } else if (isKeywordVariadic) {
        declaredType = InferredTypes.DICT;
      } else {
        declaredType = anyType();
      }
      String parameterName = parameterSymbol.hasName() ? parameterSymbol.getName() : null;
      ParameterImpl parameter = new ParameterImpl(parameterName, declaredType, null, parameterSymbol.getHasDefault(), parameterState,
        isKeywordVariadic, isPositionalVariadic, parameterSymbol.getTypeAnnotation(), null);
      parameters.add(parameter);
    }
    functionDefinitionLocation = null;
    declaredReturnType = anyType();
    isStub = true;
    isDjangoView = false;
    this.validForPythonVersions = new HashSet<>(validFor);
  }

  public FunctionSymbolImpl(FunctionDescriptor functionDescriptor, String symbolName) {
    super(symbolName, functionDescriptor.fullyQualifiedName());
    setKind(Kind.FUNCTION);
    isInstanceMethod = functionDescriptor.isInstanceMethod();
    isAsynchronous = functionDescriptor.isAsynchronous();
    hasDecorators = functionDescriptor.hasDecorators();
    decorators = functionDescriptor.decorators();
    annotatedReturnTypeName = functionDescriptor.annotatedReturnTypeName();
    functionDefinitionLocation = functionDescriptor.definitionLocation();
    // TODO: Will no longer be true once SONARPY-647 is fixed
    isStub = false;
  }

  public void setParametersWithType(ParameterList parametersList) {
    this.parameters.clear();
    createParameterNames(parametersList.all(), functionDefinitionLocation == null ? null : functionDefinitionLocation.fileId());
  }

  public void addParameter(ParameterImpl parameter) {
    this.parameters.add(parameter);
    if (parameter.isVariadic()) {
      this.hasVariadicParameter = true;
    }
  }

  FunctionSymbolImpl(String name, FunctionSymbol functionSymbol) {
    super(name, functionSymbol.fullyQualifiedName());
    setKind(Kind.FUNCTION);
    isInstanceMethod = functionSymbol.isInstanceMethod();
    isAsynchronous = functionSymbol.isAsynchronous();
    hasDecorators = functionSymbol.hasDecorators();
    decorators = functionSymbol.decorators();
    annotatedReturnTypeName = functionSymbol.annotatedReturnTypeName();
    hasVariadicParameter = functionSymbol.hasVariadicParameter();
    // TODO parameters are shallow
    parameters.addAll(functionSymbol.parameters());
    functionDefinitionLocation = functionSymbol.definitionLocation();
    FunctionSymbolImpl functionSymbolImpl = (FunctionSymbolImpl) functionSymbol;
    protobufReturnType = functionSymbolImpl.protobufReturnType;
    if (functionSymbolImpl.protobufReturnType == null || functionSymbolImpl.hasReadDeclaredReturnType) {
      declaredReturnType = functionSymbolImpl.declaredReturnType();
    }
    isStub = functionSymbol.isStub();
    isDjangoView = functionSymbolImpl.isDjangoView();
    validForPythonVersions = functionSymbolImpl.validForPythonVersions;

  }

  @Override
  public FunctionSymbolImpl copyWithoutUsages() {
    FunctionSymbolImpl copy = new FunctionSymbolImpl(name(), this);
    copy.setKind(kind());
    return copy;
  }

  private static boolean isInstanceMethod(FunctionDef functionDef) {
    return !functionDef.name().name().equals("__new__") && functionDef.isMethodDefinition() && functionDef.decorators().stream()
      .map(decorator -> TreeUtils.decoratorNameFromExpression(decorator.expression()))
      .filter(Objects::nonNull)
      .noneMatch(decorator -> decorator.equals(STATIC_METHOD_DECORATOR) || decorator.equals(CLASS_METHOD_DECORATOR));
  }

  private static List decorators(FunctionDef functionDef) {
    List decoratorNames = new ArrayList<>();
    for (Decorator decorator : functionDef.decorators()) {
      String decoratorName = TreeUtils.decoratorNameFromExpression(decorator.expression());
      if (decoratorName != null) {
        decoratorNames.add(decoratorName);
      }
    }
    return decoratorNames;
  }

  private void createParameterNames(List parameterTrees, @Nullable String fileId) {
    ParameterState parameterState = new ParameterState();
    parameterState.positionalOnly = parameterTrees.stream().anyMatch(param -> Optional.of(param)
      .filter(p -> p.is(Tree.Kind.PARAMETER))
      .map(p -> ((org.sonar.plugins.python.api.tree.Parameter) p).starToken())
      .map(Token::value)
      .filter("/"::equals)
      .isPresent()
    );
    for (AnyParameter anyParameter : parameterTrees) {
      if (anyParameter.is(Tree.Kind.PARAMETER)) {
        addParameter((org.sonar.plugins.python.api.tree.Parameter) anyParameter, fileId, parameterState);
      } else {
        parameters.add(new ParameterImpl(null, InferredTypes.anyType(), null, false, parameterState, false, false, null, locationInFile(anyParameter, fileId)));
      }
    }
  }

  private void addParameter(org.sonar.plugins.python.api.tree.Parameter parameter, @Nullable String fileId, ParameterState parameterState) {
    Name parameterName = parameter.name();
    Token starToken = parameter.starToken();
    if (parameterName != null) {
      ParameterType parameterType = getParameterType(parameter);
      this.parameters.add(new ParameterImpl(parameterName.name(), parameterType.inferredType, annotatedTypeName(parameter.typeAnnotation()), parameter.defaultValue() != null,
        parameterState, parameterType.isKeywordVariadic, parameterType.isPositionalVariadic, null, locationInFile(parameter, fileId)));
      if (starToken != null) {
        hasVariadicParameter = true;
        parameterState.keywordOnly = true;
        parameterState.positionalOnly = false;
      }
    } else if (starToken != null) {
      if ("*".equals(starToken.value())) {
        parameterState.keywordOnly = true;
        parameterState.positionalOnly = false;
      }
      if ("/".equals(starToken.value())) {
        parameterState.positionalOnly = false;
      }
    }
  }

  private ParameterType getParameterType(org.sonar.plugins.python.api.tree.Parameter parameter) {
    InferredType inferredType = InferredTypes.anyType();
    boolean isPositionalVariadic = false;
    boolean isKeywordVariadic = false;
    Token starToken = parameter.starToken();
    if (starToken != null) {
      // https://docs.python.org/3/reference/compound_stmts.html#function-definitions
      hasVariadicParameter = true;
      if ("*".equals(starToken.value())) {
        // if the form “*identifier” is present, it is initialized to a tuple receiving any excess positional parameters
        isPositionalVariadic = true;
        inferredType = InferredTypes.TUPLE;
      }
      if ("**".equals(starToken.value())) {
        //  If the form “**identifier” is present, it is initialized to a new ordered mapping receiving any excess keyword arguments
        isKeywordVariadic = true;
        inferredType = InferredTypes.DICT;
      }
    } else {
      TypeAnnotation typeAnnotation = parameter.typeAnnotation();
      if (typeAnnotation != null) {
        inferredType = isStub ? fromTypeshedTypeAnnotation(typeAnnotation) : fromTypeAnnotation(typeAnnotation);
      }
    }
    return new ParameterType(inferredType, isKeywordVariadic, isPositionalVariadic);
  }

  @Override
  public List decorators() {
    return decorators;
  }

  private static class ParameterState {
    boolean keywordOnly = false;
    boolean positionalOnly = false;
  }

  @Override
  public List parameters() {
    return parameters;
  }

  @Override
  public boolean isStub() {
    return isStub;
  }

  @Override
  public boolean isAsynchronous() {
    return isAsynchronous;
  }

  @Override
  public boolean hasVariadicParameter() {
    return hasVariadicParameter;
  }

  @Override
  public boolean isInstanceMethod() {
    return isInstanceMethod;
  }

  @Override
  public boolean hasDecorators() {
    return hasDecorators;
  }

  @Override
  public LocationInFile definitionLocation() {
    return functionDefinitionLocation;
  }

  public InferredType declaredReturnType() {
    if (!hasReadDeclaredReturnType && protobufReturnType != null) {
      declaredReturnType = InferredTypes.fromTypeshedProtobuf(protobufReturnType);
      hasReadDeclaredReturnType = true;
    }
    return declaredReturnType;
  }

  public SymbolsProtos.Type protobufReturnType() {
    return protobufReturnType;
  }
  
  static class ParameterType {
    InferredType inferredType;
    boolean isPositionalVariadic;
    boolean isKeywordVariadic;

    public ParameterType(InferredType inferredType, boolean isKeywordVariadic, boolean isPositionalVariadic) {
      this.inferredType = inferredType;
      this.isKeywordVariadic = isKeywordVariadic;
      this.isPositionalVariadic = isPositionalVariadic;
    }
  }

  private String annotatedTypeName(@Nullable TypeAnnotation typeAnnotation) {
    return Optional.ofNullable(typeAnnotation)
      .map(TypeAnnotation::expression)
      .map(SymbolImpl::getTypeSymbolFromExpression)
      .map(Symbol::fullyQualifiedName)
      .orElse(null);
  }

  @Override
  public String annotatedReturnTypeName() {
    return annotatedReturnTypeName;
  }

  public void setDeclaredReturnType(InferredType declaredReturnType) {
    this.declaredReturnType = declaredReturnType;
  }

  public Symbol owner() {
    return owner;
  }

  public void setOwner(Symbol owner) {
    this.owner = owner;
  }

  public boolean isDjangoView() {
    return isDjangoView;
  }

  public void setIsDjangoView(boolean isDjangoView) {
    this.isDjangoView = isDjangoView;
  }

  public static class ParameterImpl implements Parameter {

    private final String name;
    private InferredType declaredType;
    private SymbolsProtos.Type protobufType;
    private final String annotatedTypeName;
    private final boolean hasDefaultValue;
    private final boolean isKeywordVariadic;
    private final boolean isPositionalVariadic;
    private final boolean isKeywordOnly;
    private final boolean isPositionalOnly;
    private final LocationInFile location;
    private boolean hasReadDeclaredType = false;

    ParameterImpl(@Nullable String name, InferredType declaredType, @Nullable String annotatedTypeName, boolean hasDefaultValue,
      ParameterState parameterState, boolean isKeywordVariadic, boolean isPositionalVariadic, @Nullable SymbolsProtos.Type protobufType, @Nullable LocationInFile location) {
      this.name = name;
      this.declaredType = declaredType;
      this.hasDefaultValue = hasDefaultValue;
      this.isKeywordVariadic = isKeywordVariadic;
      this.isPositionalVariadic = isPositionalVariadic;
      this.isKeywordOnly = parameterState.keywordOnly;
      this.isPositionalOnly = parameterState.positionalOnly;
      this.location = location;
      this.protobufType = protobufType;
      this.annotatedTypeName = annotatedTypeName;
    }

    public ParameterImpl(FunctionDescriptor.Parameter parameterDescriptor) {
      this.name = parameterDescriptor.name();
      this.hasDefaultValue = parameterDescriptor.hasDefaultValue();
      this.isPositionalVariadic = parameterDescriptor.isPositionalVariadic();
      this.isKeywordVariadic = parameterDescriptor.isKeywordVariadic();
      this.isKeywordOnly = parameterDescriptor.isKeywordOnly();
      this.isPositionalOnly = parameterDescriptor.isPositionalOnly();
      this.location = parameterDescriptor.location();
      this.annotatedTypeName = parameterDescriptor.annotatedType();
    }

    @Override
    @CheckForNull
    public String name() {
      return name;
    }

    @Override
    public InferredType declaredType() {
      if (!hasReadDeclaredType && protobufType != null) {
        declaredType = InferredTypes.fromTypeshedProtobuf(protobufType);
        hasReadDeclaredType = true;
      }
      return declaredType;
    }

    public void setDeclaredType(InferredType type) {
      this.declaredType = type;
    }

    @CheckForNull
    public String annotatedTypeName() {
      return annotatedTypeName;
    }

    @Override
    public boolean hasDefaultValue() {
      return hasDefaultValue;
    }

    @Override
    public boolean isVariadic() {
      return isKeywordVariadic || isPositionalVariadic;
    }

    @Override
    public boolean isKeywordOnly() {
      return isKeywordOnly;
    }

    @Override
    public boolean isPositionalOnly() {
      return isPositionalOnly;
    }

    @Override
    public boolean isKeywordVariadic() {
      return isKeywordVariadic;
    }

    @Override
    public boolean isPositionalVariadic() {
      return isPositionalVariadic;
    }

    @CheckForNull
    @Override
    public LocationInFile location() {
      return location;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy