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

org.sonar.python.semantic.ProjectLevelSymbolTable 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * 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.python.semantic;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.PythonFile;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.FileInput;
import org.sonar.plugins.python.api.tree.RegularArgument;
import org.sonar.python.index.AmbiguousDescriptor;
import org.sonar.python.index.Descriptor;
import org.sonar.python.index.DescriptorUtils;
import org.sonar.python.semantic.v2.BasicTypeTable;
import org.sonar.python.semantic.v2.ProjectLevelTypeTable;
import org.sonar.python.semantic.v2.SymbolTableBuilderV2;
import org.sonar.python.semantic.v2.TypeInferenceV2;
import org.sonar.python.semantic.v2.UsageV2;
import org.sonar.python.semantic.v2.converter.PythonTypeToDescriptorConverter;
import org.sonar.python.semantic.v2.typeshed.TypeShedDescriptorsProvider;
import org.sonar.python.types.v2.FunctionType;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TriBool;
import org.sonar.python.types.v2.TypeChecker;
import org.sonar.python.types.v2.UnknownType;

import static org.sonar.python.tree.TreeUtils.nthArgumentOrKeyword;

public class ProjectLevelSymbolTable {

  private final PythonTypeToDescriptorConverter pythonTypeToDescriptorConverter = new PythonTypeToDescriptorConverter();
  private final Map> globalDescriptorsByModuleName;
  private Map globalDescriptorsByFQN;
  private final Set djangoViewsFQN = new HashSet<>();
  private final Map> importsByModule = new HashMap<>();
  private final Set projectBasePackages = new HashSet<>();
  private TypeShedDescriptorsProvider typeShedDescriptorsProvider = null;
  private Set cachedSymbols = null;

  public static ProjectLevelSymbolTable empty() {
    return new ProjectLevelSymbolTable();
  }

  public static ProjectLevelSymbolTable from(Map> globalDescriptorsByModuleName) {
    var projectLevelSymbolTable = ProjectLevelSymbolTable.empty();

    for (var entry : globalDescriptorsByModuleName.entrySet()) {
      var descriptors = entry.getValue();
      projectLevelSymbolTable.globalDescriptorsByModuleName.put(entry.getKey(), descriptors);
    }

    return projectLevelSymbolTable;
  }

  private ProjectLevelSymbolTable() {
    this.globalDescriptorsByModuleName = new HashMap<>();
  }

  public void removeModule(String packageName, String fileName) {
    String fullyQualifiedModuleName = SymbolUtils.fullyQualifiedModuleName(packageName, fileName);
    globalDescriptorsByModuleName.remove(fullyQualifiedModuleName);
    // ensure globalDescriptorsByFQN is re-computed
    this.globalDescriptorsByFQN = null;
  }

  public void addModule(FileInput fileInput, String packageName, PythonFile pythonFile) {
    String fullyQualifiedModuleName = SymbolUtils.fullyQualifiedModuleName(packageName, pythonFile.fileName());
    var symbolTable = new SymbolTableBuilderV2(fileInput).build();
    var typeInferenceV2 = new TypeInferenceV2(new BasicTypeTable(new ProjectLevelTypeTable(this)), pythonFile, symbolTable, packageName);
    var typesBySymbol = typeInferenceV2.inferTypes(fileInput);
    importsByModule.put(fullyQualifiedModuleName, typeInferenceV2.importedModulesFQN());
    var moduleDescriptors = typesBySymbol.entrySet()
      .stream()
      .filter(entry -> isNotMissingType(entry.getValue()))
      .map(entry -> {
          var descriptor = pythonTypeToDescriptorConverter.convert(fullyQualifiedModuleName, entry.getKey(), entry.getValue());
          return Map.entry(entry.getKey(), descriptor);
        }
      )
      .filter(entry -> !(!Objects.requireNonNull(entry.getValue().fullyQualifiedName()).startsWith(fullyQualifiedModuleName)
        || entry.getKey().usages().stream().anyMatch(u -> u.kind().equals(UsageV2.Kind.IMPORT))))
      .map(Map.Entry::getValue)
      .collect(Collectors.toSet());
    globalDescriptorsByModuleName.put(fullyQualifiedModuleName, moduleDescriptors);
    addModuleToGlobalSymbolsByFQN(moduleDescriptors);

    DjangoViewsVisitor djangoViewsVisitor = new DjangoViewsVisitor(fullyQualifiedModuleName);
    fileInput.accept(djangoViewsVisitor);
  }

  private static boolean isNotMissingType(Set types) {
    return !types.isEmpty() && types.stream().noneMatch(UnknownType.UnresolvedImportType.class::isInstance);
  }

  private void addModuleToGlobalSymbolsByFQN(Set descriptors) {
    Map moduleDescriptorsByFQN = descriptors.stream()
      .filter(d -> d.fullyQualifiedName() != null)
      .collect(Collectors.toMap(Descriptor::fullyQualifiedName, Function.identity(), AmbiguousDescriptor::create));
    globalDescriptorsByFQN().putAll(moduleDescriptorsByFQN);
  }

  private Map globalDescriptorsByFQN() {
    if (globalDescriptorsByFQN == null) {
      globalDescriptorsByFQN = globalDescriptorsByModuleName.values()
        .stream()
        .flatMap(Collection::stream)
        .filter(descriptor -> descriptor.fullyQualifiedName() != null)
        .collect(Collectors.toMap(Descriptor::fullyQualifiedName, Function.identity(), AmbiguousDescriptor::create));
    }
    return globalDescriptorsByFQN;
  }

  @CheckForNull
  public Symbol getSymbol(@Nullable String fullyQualifiedName) {
    return getSymbol(fullyQualifiedName, null);
  }

  public Symbol getSymbol(@Nullable String fullyQualifiedName, @Nullable String localSymbolName) {
    return getSymbol(fullyQualifiedName, localSymbolName, new HashMap<>(), new HashMap<>());
  }

  public Symbol getSymbol(@Nullable String fullyQualifiedName, @Nullable String localSymbolName,
                          Map createdSymbolsByDescriptor, Map createdSymbolsByFqn) {
    if (fullyQualifiedName == null) return null;
    Descriptor descriptor = globalDescriptorsByFQN().get(fullyQualifiedName);
    return descriptor == null ? null : DescriptorUtils.symbolFromDescriptor(descriptor, this, localSymbolName, createdSymbolsByDescriptor, createdSymbolsByFqn);
  }

  @CheckForNull
  public Set getSymbolsFromModule(@Nullable String moduleName) {
    Set descriptors = globalDescriptorsByModuleName.get(moduleName);
    if (descriptors == null) {
      return null;
    }
    Map createdSymbolsByDescriptor = new HashMap<>();
    Map createdSymbolsByFqn = new HashMap<>();
    return descriptors.stream()
      .map(desc -> DescriptorUtils.symbolFromDescriptor(desc, this, null, createdSymbolsByDescriptor, createdSymbolsByFqn)).collect(Collectors.toSet());
  }

  @CheckForNull
  public Set getDescriptorsFromModule(@Nullable String moduleName) {
    return globalDescriptorsByModuleName.get(moduleName);
  }

  public Map> importsByModule() {
    return Collections.unmodifiableMap(importsByModule);
  }

  public void insertEntry(String moduleName, Set descriptors) {
    this.globalDescriptorsByModuleName.put(moduleName, descriptors);
  }

  @CheckForNull
  public Set descriptorsForModule(String moduleName) {
    return globalDescriptorsByModuleName.get(moduleName);
  }

  public boolean isDjangoView(@Nullable String fqn) {
    return djangoViewsFQN.contains(fqn);
  }

  public void addProjectPackage(String projectPackage) {
    projectBasePackages.add(projectPackage.split("\\.", 2)[0]);
  }

  public Set projectBasePackages() {
    return projectBasePackages;
  }

  public TypeShedDescriptorsProvider typeShedDescriptorsProvider() {
    if (typeShedDescriptorsProvider == null) {
      typeShedDescriptorsProvider = new TypeShedDescriptorsProvider(projectBasePackages);
    }
    return typeShedDescriptorsProvider;
  }

  /**
   * Returns stub symbols to be used by SonarSecurity.
   * Ambiguous symbols that only contain class symbols are disambiguated with latest Python version.
   */
  public Collection stubFilesSymbols() {
    if (cachedSymbols != null) {
      return cachedSymbols;
    }
    Map symbolsByFqn = new HashMap<>();
    cachedSymbols = new HashSet<>();
    for (Descriptor descriptor : typeShedDescriptorsProvider.stubFilesDescriptors()) {
      if (descriptor.fullyQualifiedName() != null) {
        Symbol symbol = symbolsByFqn.computeIfAbsent(descriptor.fullyQualifiedName(), k ->
          DescriptorUtils.symbolFromDescriptor(descriptor, this, null, new HashMap<>(), new HashMap<>()));
        cachedSymbols.add(symbol);
      }
    }
    return cachedSymbols;
  }

  private class DjangoViewsVisitor extends BaseTreeVisitor {

    String fullyQualifiedModuleName;

    public DjangoViewsVisitor(String fullyQualifiedModuleName) {
      this.fullyQualifiedModuleName = fullyQualifiedModuleName;
    }

    @Override
    public void visitCallExpression(CallExpression callExpression) {
      super.visitCallExpression(callExpression);
      if (isCallRegisteringDjangoView(callExpression)) {
        RegularArgument viewArgument = nthArgumentOrKeyword(1, "view", callExpression.arguments());
        if (viewArgument != null) {
          PythonType pythonType = viewArgument.expression().typeV2();
          if (pythonType instanceof UnknownType.UnresolvedImportType unresolvedImportType) {
            String importPath = unresolvedImportType.importPath();
            djangoViewsFQN.add(importPath);
          } else if (pythonType instanceof FunctionType functionType) {
            djangoViewsFQN.add(functionType.fullyQualifiedName());
          }
        }
      }
    }

    private boolean isCallRegisteringDjangoView(CallExpression callExpression) {
      TypeChecker typeChecker = new TypeChecker(new BasicTypeTable(new ProjectLevelTypeTable(ProjectLevelSymbolTable.this)));
      TriBool isConfPathCall = typeChecker.typeCheckBuilder().isTypeWithName("django.urls.conf.path").check(callExpression.callee().typeV2());
      TriBool isPathCall = typeChecker.typeCheckBuilder().isTypeWithName("django.urls.path").check(callExpression.callee().typeV2());
      return isConfPathCall.equals(TriBool.TRUE) || isPathCall.equals(TriBool.TRUE);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy