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

com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex Maven / Gradle / Ivy

Go to download

Language Server Protocol implementation for 1C (BSL) - 1C:Enterprise 8 and OneScript languages.

There is a newer version: 0.24.0-rc.1
Show newest version
/*
 * This file is a part of BSL Language Server.
 *
 * Copyright (c) 2018-2023
 * Alexey Sosnoviy , Nikita Fedkin  and contributors
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 *
 * BSL Language Server 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.0 of the License, or (at your option) any later version.
 *
 * BSL Language Server 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 BSL Language Server.
 */
package com.github._1c_syntax.bsl.languageserver.references;

import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;
import com.github._1c_syntax.bsl.languageserver.context.ServerContext;
import com.github._1c_syntax.bsl.languageserver.context.symbol.Exportable;
import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol;
import com.github._1c_syntax.bsl.languageserver.context.symbol.SymbolTree;
import com.github._1c_syntax.bsl.languageserver.references.model.Location;
import com.github._1c_syntax.bsl.languageserver.references.model.LocationRepository;
import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType;
import com.github._1c_syntax.bsl.languageserver.references.model.Reference;
import com.github._1c_syntax.bsl.languageserver.references.model.Symbol;
import com.github._1c_syntax.bsl.languageserver.references.model.SymbolOccurrence;
import com.github._1c_syntax.bsl.languageserver.references.model.SymbolOccurrenceRepository;
import com.github._1c_syntax.bsl.languageserver.utils.MdoRefBuilder;
import com.github._1c_syntax.bsl.languageserver.utils.Ranges;
import com.github._1c_syntax.bsl.types.ModuleType;
import com.github._1c_syntax.utils.StringInterner;
import lombok.RequiredArgsConstructor;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolKind;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
public class ReferenceIndex {

  private final ServerContext serverContext;
  private final StringInterner stringInterner;

  private final LocationRepository locationRepository;
  private final SymbolOccurrenceRepository symbolOccurrenceRepository;

  /**
   * Получить ссылки на символ.
   *
   * @param symbol Символ, для которого необходимо осуществить поиск ссылок.
   * @return Список ссылок на символ.
   */
  public List getReferencesTo(SourceDefinedSymbol symbol) {
    var mdoRef = MdoRefBuilder.getMdoRef(symbol.getOwner());
    var moduleType = symbol.getOwner().getModuleType();
    var symbolName = symbol.getName().toLowerCase(Locale.ENGLISH);
    String scopeName = "";

    if (symbol.getSymbolKind() == SymbolKind.Variable) {
      scopeName = symbol.getRootParent(SymbolKind.Method)
        .map(SourceDefinedSymbol::getName)
        .map(name -> name.toLowerCase(Locale.ENGLISH))
        .orElse("");
    }

    var symbolDto = Symbol.builder()
      .mdoRef(mdoRef)
      .moduleType(moduleType)
      .scopeName(scopeName)
      .symbolKind(symbol.getSymbolKind())
      .symbolName(symbolName)
      .build();

    return symbolOccurrenceRepository.getAllBySymbol(symbolDto)
      .stream()
      .map(this::buildReference)
      .flatMap(Optional::stream)
      .collect(Collectors.toList());
  }

  /**
   * Поиск символа по позиции курсора.
   *
   * @param uri      URI документа, в котором необходимо осуществить поиск.
   * @param position позиция курсора.
   * @return данные ссылки.
   */
  public Optional getReference(URI uri, Position position) {
    return locationRepository.getSymbolOccurrencesByLocationUri(uri)
      .filter(symbolOccurrence -> Ranges.containsPosition(symbolOccurrence.getLocation().getRange(), position))
      .findAny()
      .flatMap(this::buildReference);
  }

  /**
   * Поиск ссылок на символы в документе.
   *
   * @param uri URI документа, в котором нужно найти ссылки на другие символы.
   * @return Список ссылок на символы.
   */
  public List getReferencesFrom(URI uri) {

    return locationRepository.getSymbolOccurrencesByLocationUri(uri)
      .map(this::buildReference)
      .flatMap(Optional::stream)
      .collect(Collectors.toList());
  }

  /**
   * Поиск ссылок на символы в документе.
   *
   * @param uri URI документа, в котором нужно найти ссылки на другие символы.
   * @return Список ссылок на символы.
   */
  public List getReferencesFrom(URI uri, SymbolKind kind) {

    return locationRepository.getSymbolOccurrencesByLocationUri(uri)
      .filter(s -> s.getSymbol().getSymbolKind() == kind)
      .map(this::buildReference)
      .flatMap(Optional::stream)
      .collect(Collectors.toList());
  }

  /**
   * Поиск ссылок на символы в символе.
   *
   * @param symbol Символ, в котором нужно найти ссылки на другие символы.
   * @return Список ссылок на символы.
   */
  public List getReferencesFrom(SourceDefinedSymbol symbol) {
    return getReferencesFrom(symbol.getOwner().getUri()).stream()
      .filter(reference -> reference.getFrom().equals(symbol))
      .collect(Collectors.toList());
  }

  /**
   * Очистить ссылки из/на текущий документ.
   *
   * @param uri URI документа.
   */
  public void clearReferences(URI uri) {
    var symbolOccurrences = locationRepository.getSymbolOccurrencesByLocationUri(uri);
    symbolOccurrenceRepository.deleteAll(symbolOccurrences.collect(Collectors.toSet()));
    locationRepository.delete(uri);
  }

  /**
   * Добавить вызов метода в индекс.
   *
   * @param uri        URI документа, откуда произошел вызов.
   * @param mdoRef     Ссылка на объект-метаданных, к которому происходит обращение (например, CommonModule.ОбщийМодуль1).
   * @param moduleType Тип модуля, к которому происходит обращение (например, {@link ModuleType#CommonModule}).
   * @param symbolName Имя символа, к которому происходит обращение.
   * @param range      Диапазон, в котором происходит обращение к символу.
   */
  public void addMethodCall(URI uri, String mdoRef, ModuleType moduleType, String symbolName, Range range) {
    String symbolNameCanonical = stringInterner.intern(symbolName.toLowerCase(Locale.ENGLISH));

    var symbol = Symbol.builder()
      .mdoRef(mdoRef)
      .moduleType(moduleType)
      .scopeName("")
      .symbolKind(SymbolKind.Method)
      .symbolName(symbolNameCanonical)
      .build()
      .intern();

    var location = new Location(uri, range);
    var symbolOccurrence = SymbolOccurrence.builder()
      .occurrenceType(OccurrenceType.REFERENCE)
      .symbol(symbol)
      .location(location)
      .build();

    symbolOccurrenceRepository.save(symbolOccurrence);
    locationRepository.updateLocation(symbolOccurrence);
  }

  /**
   * Добавить обращение к переменной в индекс.
   *
   * @param uri          URI документа, откуда произошел вызов.
   * @param mdoRef       Ссылка на объект-метаданных, к которому происходит обращение (например, CommonModule.ОбщийМодуль1).
   * @param moduleType   Тип модуля, к которому происходит обращение (например, {@link ModuleType#CommonModule}).
   * @param methodName   Имя метода, к которому относиться перменная. Пустой если переменная относиться к модулю.
   * @param variableName Имя переменной, к которой происходит обращение.
   * @param range        Диапазон, в котором происходит обращение к символу.
   * @param definition     Признак обновления значения переменной.
   */
  public void addVariableUsage(URI uri,
                               String mdoRef,
                               ModuleType moduleType,
                               String methodName,
                               String variableName,
                               Range range,
                               boolean definition) {
    String methodNameCanonical = stringInterner.intern(methodName.toLowerCase(Locale.ENGLISH));
    String variableNameCanonical = stringInterner.intern(variableName.toLowerCase(Locale.ENGLISH));

    var symbol = Symbol.builder()
      .mdoRef(mdoRef)
      .moduleType(moduleType)
      .scopeName(methodNameCanonical)
      .symbolKind(SymbolKind.Variable)
      .symbolName(variableNameCanonical)
      .build()
      .intern();

    var location = new Location(uri, range);

    var symbolOccurrence = SymbolOccurrence.builder()
      .occurrenceType(definition ? OccurrenceType.DEFINITION : OccurrenceType.REFERENCE)
      .symbol(symbol)
      .location(location)
      .build();

    symbolOccurrenceRepository.save(symbolOccurrence);
    locationRepository.updateLocation(symbolOccurrence);
  }

  private Optional buildReference(
    SymbolOccurrence symbolOccurrence
  ) {

    var uri = symbolOccurrence.getLocation().getUri();
    var range = symbolOccurrence.getLocation().getRange();
    var occurrenceType = symbolOccurrence.getOccurrenceType();

    return getSourceDefinedSymbol(symbolOccurrence.getSymbol())
      .map((SourceDefinedSymbol symbol) -> {
        SourceDefinedSymbol from = getFromSymbol(symbolOccurrence);
        return new Reference(from, symbol, uri, range, occurrenceType);
      })
      .filter(ReferenceIndex::isReferenceAccessible);
  }

  private Optional getSourceDefinedSymbol(Symbol symbolEntity) {
    String mdoRef = symbolEntity.getMdoRef();
    ModuleType moduleType = symbolEntity.getModuleType();
    String symbolName = symbolEntity.getSymbolName();

    if (symbolEntity.getSymbolKind() == SymbolKind.Variable) {
      return serverContext.getDocument(mdoRef, moduleType)
        .map(DocumentContext::getSymbolTree)
        .flatMap(symbolTree -> symbolTree.getMethodSymbol(symbolEntity.getScopeName())
        .flatMap(method -> symbolTree.getVariableSymbol(symbolName, method))
        .or(() -> symbolTree.getVariableSymbol(symbolName, symbolTree.getModule())));
    }

    return serverContext.getDocument(mdoRef, moduleType)
      .map(DocumentContext::getSymbolTree)
      .flatMap(symbolTree -> symbolTree.getMethodSymbol(symbolName));
  }

  private SourceDefinedSymbol getFromSymbol(SymbolOccurrence symbolOccurrence) {

    var uri = symbolOccurrence.getLocation().getUri();
    var position = symbolOccurrence.getLocation().getRange().getStart();

    Optional symbolTree = Optional.ofNullable(serverContext.getDocument(uri))
      .map(DocumentContext::getSymbolTree);
    return symbolTree
      .map(SymbolTree::getChildrenFlat)
      .stream()
      .flatMap(Collection::stream)
      .filter(sourceDefinedSymbol -> sourceDefinedSymbol.getSymbolKind() != SymbolKind.Namespace)
      .filter(symbol -> Ranges.containsPosition(symbol.getRange(), position))
      .findFirst()
      .or(() -> symbolTree.map(SymbolTree::getModule))
      .orElseThrow();
  }

  private static boolean isReferenceAccessible(Reference reference) {
    if (!reference.isSourceDefinedSymbolReference()) {
      return true;
    }

    SourceDefinedSymbol to = reference.getSourceDefinedSymbol().orElseThrow();
    SourceDefinedSymbol from = reference.getFrom();
    if (to.getOwner().equals(from.getOwner())) {
      return true;
    }

    if (to instanceof Exportable) {
      return ((Exportable) to).isExport();
    }

    return true;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy