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

com.github._1c_syntax.bsl.languageserver.references.ReferenceIndexFiller 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.events.DocumentContextContentChangedEvent;
import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol;
import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol;
import com.github._1c_syntax.bsl.languageserver.utils.MdoRefBuilder;
import com.github._1c_syntax.bsl.languageserver.utils.Methods;
import com.github._1c_syntax.bsl.languageserver.utils.Modules;
import com.github._1c_syntax.bsl.languageserver.utils.NotifyDescription;
import com.github._1c_syntax.bsl.languageserver.utils.Ranges;
import com.github._1c_syntax.bsl.languageserver.utils.Strings;
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
import com.github._1c_syntax.bsl.parser.BSLParser;
import com.github._1c_syntax.bsl.parser.BSLParserBaseVisitor;
import com.github._1c_syntax.bsl.parser.BSLParserRuleContext;
import com.github._1c_syntax.bsl.types.ModuleType;
import edu.umd.cs.findbugs.annotations.Nullable;
import lombok.RequiredArgsConstructor;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolKind;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
public class ReferenceIndexFiller {

  private static final Set DEFAULT_MODULE_TYPES = EnumSet.of(
    ModuleType.ManagerModule,
    ModuleType.CommonModule,
    ModuleType.UNKNOWN
  );

  private final ReferenceIndex index;

  @EventListener
  public void handleEvent(DocumentContextContentChangedEvent event) {
    var documentContext = event.getSource();
    if (documentContext.isComputedDataFrozen()) {
      return;
    }
    fill(documentContext);
  }

  public void fill(DocumentContext documentContext) {
    index.clearReferences(documentContext.getUri());
    BSLParser.FileContext documentContextAst = documentContext.getAst();
    new MethodSymbolReferenceIndexFinder(documentContext).visitFile(documentContextAst);
    new VariableSymbolReferenceIndexFinder(documentContext).visitFile(documentContextAst);
  }

  @RequiredArgsConstructor
  private class MethodSymbolReferenceIndexFinder extends BSLParserBaseVisitor {

    private final DocumentContext documentContext;
    private Set commonModuleMdoRefFromSubParams = Collections.emptySet();

    @Override
    public BSLParserRuleContext visitProcDeclaration(BSLParser.ProcDeclarationContext ctx) {
      commonModuleMdoRefFromSubParams = calcParams(ctx.paramList());
      return super.visitProcDeclaration(ctx);
    }

    @Override
    public BSLParserRuleContext visitFuncDeclaration(BSLParser.FuncDeclarationContext ctx) {
      commonModuleMdoRefFromSubParams = calcParams(ctx.paramList());
      return super.visitFuncDeclaration(ctx);
    }

    @Override
    public BSLParserRuleContext visitCallStatement(BSLParser.CallStatementContext ctx) {
      if (ctx.globalMethodCall() != null) {
        // see visitGlobalMethodCall
        return super.visitCallStatement(ctx);
      }

      String mdoRef = MdoRefBuilder.getMdoRef(documentContext, ctx);
      if (mdoRef.isEmpty()) {
        return super.visitCallStatement(ctx);
      }

      Methods.getMethodName(ctx).ifPresent(methodName -> checkCall(mdoRef, methodName));

      return super.visitCallStatement(ctx);
    }

    @Override
    public BSLParserRuleContext visitComplexIdentifier(BSLParser.ComplexIdentifierContext ctx) {
      var mdoRef = MdoRefBuilder.getMdoRef(documentContext, ctx);
      if (mdoRef.isEmpty()) {
        return super.visitComplexIdentifier(ctx);
      }

      Methods.getMethodName(ctx).ifPresent(methodName -> checkCall(mdoRef, methodName));
      return super.visitComplexIdentifier(ctx);
    }

    @Override
    public BSLParserRuleContext visitGlobalMethodCall(BSLParser.GlobalMethodCallContext ctx) {
      var mdoRef = MdoRefBuilder.getMdoRef(documentContext);
      var moduleType = documentContext.getModuleType();
      var methodName = ctx.methodName().getStart();
      var methodNameText = methodName.getText();

      documentContext.getSymbolTree().getMethodSymbol(methodNameText)
        .ifPresent(methodSymbol -> addMethodCall(mdoRef, moduleType, methodNameText, Ranges.create(methodName)));

      return super.visitGlobalMethodCall(ctx);
    }

    @Override
    public BSLParserRuleContext visitNewExpression(BSLParser.NewExpressionContext ctx) {
      if (NotifyDescription.isNotifyDescription(ctx)) {
        final var doCallContext = ctx.doCall();
        if (doCallContext == null) {
          return super.visitNewExpression(ctx);
        }
        var callParamList = doCallContext.callParamList().callParam();

        if (NotifyDescription.notifyDescriptionContainsHandler(callParamList)) {
          addCallbackMethodCall(
            callParamList.get(NotifyDescription.HANDLER_INDEX),
            getModule(callParamList.get(NotifyDescription.HANDLER_MODULE_INDEX))
          );
        }

        if (NotifyDescription.notifyDescriptionContainsErrorHandler(callParamList)) {
          addCallbackMethodCall(
            callParamList.get(NotifyDescription.HANDLER_ERROR_INDEX),
            getModule(callParamList.get(NotifyDescription.HANDLER_ERROR_MODULE_INDEX))
          );
        }

        return super.visitNewExpression(ctx);
      }

      return super.visitNewExpression(ctx);
    }

    @Override
    public BSLParserRuleContext visitLValue(BSLParser.LValueContext ctx) {
      final var identifier = ctx.IDENTIFIER();
      if (identifier != null) {
        final List modifiers = Optional.ofNullable(ctx.acceptor())
          .map(BSLParser.AcceptorContext::modifier)
          .orElseGet(Collections::emptyList);
        var mdoRef = MdoRefBuilder.getMdoRef(documentContext, identifier, modifiers);
        if (!mdoRef.isEmpty()) {
          Methods.getMethodName(ctx).ifPresent(methodName -> checkCall(mdoRef, methodName));
        }
      }
      return super.visitLValue(ctx);
    }

    private void checkCall(String mdoRef, Token methodName) {
      var methodNameText = Strings.trimQuotes(methodName.getText());
      final var configuration = documentContext.getServerContext().getConfiguration();
      Map modules = configuration.mdoModuleTypes(mdoRef);
      for (ModuleType moduleType : modules.keySet()) {
        if (!DEFAULT_MODULE_TYPES.contains(moduleType)
          || (moduleType == ModuleType.CommonModule && commonModuleMdoRefFromSubParams.contains(mdoRef))) {
          continue;
        }
        addMethodCall(mdoRef, moduleType, methodNameText, Ranges.create(methodName));
      }
    }

    private void addMethodCall(String mdoRef, ModuleType moduleType, String methodName, Range range) {
      index.addMethodCall(documentContext.getUri(), mdoRef, moduleType, methodName, range);
    }

    private void addCallbackMethodCall(BSLParser.CallParamContext methodName, String mdoRef) {
      // todo: move this out of method 
      if (mdoRef.isEmpty()) {
        return;
      }
      Methods.getMethodName(methodName).ifPresent((Token methodNameToken) -> {
        if (!mdoRef.equals(MdoRefBuilder.getMdoRef(documentContext))) {
          checkCall(mdoRef, methodNameToken);
        }

        addMethodCall(
          mdoRef,
          documentContext.getModuleType(),
          Strings.trimQuotes(methodName.getText()),
          Ranges.create(methodName)
        );
      });
    }

    private String getModule(BSLParser.CallParamContext callParamContext) {
      final var complexIdentifierContext1 = NotifyDescription.getFirstMember(callParamContext)
        .map(BSLParser.MemberContext::complexIdentifier)
        .filter(complexIdentifierContext -> complexIdentifierContext.IDENTIFIER() != null)
        .filter(complexIdentifierContext -> complexIdentifierContext.modifier().isEmpty());
      if (complexIdentifierContext1.isEmpty()) {
        return "";
      }
      return complexIdentifierContext1
        .filter(Predicate.not(Modules::isThisObject))
        .map(complexIdentifier -> MdoRefBuilder.getMdoRef(documentContext, complexIdentifier))
        .orElse(MdoRefBuilder.getMdoRef(documentContext));
    }

    private Set calcParams(@Nullable BSLParser.ParamListContext paramList) {
      if (paramList == null) {
        return Collections.emptySet();
      }
      final var configuration = documentContext.getServerContext().getConfiguration();
      return paramList.param().stream()
        .map(BSLParser.ParamContext::IDENTIFIER)
        .filter(Objects::nonNull)
        .map(ParseTree::getText)
        .map(configuration::findCommonModule)
        .filter(Optional::isPresent)
        .flatMap(Optional::stream)
        .map(mdCommonModule -> mdCommonModule.getMdoReference().getMdoRef())
        .collect(Collectors.toSet());
    }
  }

  @RequiredArgsConstructor
  private class VariableSymbolReferenceIndexFinder extends BSLParserBaseVisitor {

    private final DocumentContext documentContext;
    private SourceDefinedSymbol currentScope;

    @Override
    public BSLParserRuleContext visitModuleVarDeclaration(BSLParser.ModuleVarDeclarationContext ctx) {
      findVariableSymbol(ctx.var_name().getText()).ifPresent(s -> {
        if (notVariableInitialization(ctx, s)) {
          addVariableUsage(
            s.getRootParent(SymbolKind.Method),
            ctx.var_name().getText(),
            Ranges.create(ctx.var_name()),
            false
          );
        }
      });

      return ctx;
    }

    @Override
    public BSLParserRuleContext visitSub(BSLParser.SubContext ctx) {
      currentScope = documentContext.getSymbolTree().getModule();

      if (!Trees.nodeContainsErrors(ctx)) {
        documentContext
          .getSymbolTree()
          .getMethodSymbol(ctx)
          .ifPresent(scope -> currentScope = scope);
      }

      var result = super.visitSub(ctx);
      currentScope = documentContext.getSymbolTree().getModule();
      return result;
    }

    @Override
    public BSLParserRuleContext visitLValue(BSLParser.LValueContext ctx) {
      if (ctx.IDENTIFIER() == null) {
        return super.visitLValue(ctx);
      }

      findVariableSymbol(ctx.IDENTIFIER().getText()).ifPresent((VariableSymbol s) -> {
        if (notVariableInitialization(ctx, s)) {
          addVariableUsage(
            s.getRootParent(SymbolKind.Method),
            ctx.IDENTIFIER().getText(),
            Ranges.create(ctx.IDENTIFIER()),
            ctx.acceptor() != null
          );
        }
      });

      return super.visitLValue(ctx);
    }

    @Override
    public BSLParserRuleContext visitCallStatement(BSLParser.CallStatementContext ctx) {
      if (ctx.IDENTIFIER() == null) {
        return super.visitCallStatement(ctx);
      }

      var variableName = ctx.IDENTIFIER().getText();
      findVariableSymbol(variableName)
        .ifPresent(s -> addVariableUsage(
            s.getRootParent(SymbolKind.Method), variableName, Ranges.create(ctx.IDENTIFIER()), true
          )
        );
      return super.visitCallStatement(ctx);
    }

    @Override
    public BSLParserRuleContext visitComplexIdentifier(BSLParser.ComplexIdentifierContext ctx) {
      if (ctx.IDENTIFIER() == null) {
        return super.visitComplexIdentifier(ctx);
      }

      var variableName = ctx.IDENTIFIER().getText();
      findVariableSymbol(variableName)
        .ifPresent(s -> addVariableUsage(
            s.getRootParent(SymbolKind.Method), variableName, Ranges.create(ctx.IDENTIFIER()), true
          )
        );
      return super.visitComplexIdentifier(ctx);
    }

    @Override
    public BSLParserRuleContext visitForStatement(BSLParser.ForStatementContext ctx) {
      if (ctx.IDENTIFIER() == null) {
        return super.visitForStatement(ctx);
      }

      findVariableSymbol(ctx.IDENTIFIER().getText()).ifPresent((VariableSymbol s) -> {
        if (notVariableInitialization(ctx, s)) {
          addVariableUsage(
            s.getRootParent(SymbolKind.Method),
            ctx.IDENTIFIER().getText(),
            Ranges.create(ctx.IDENTIFIER()),
            false
          );
        }
      });

      return super.visitForStatement(ctx);
    }

    @Override
    public BSLParserRuleContext visitForEachStatement(BSLParser.ForEachStatementContext ctx) {
      if (ctx.IDENTIFIER() == null) {
        return super.visitForEachStatement(ctx);
      }

      findVariableSymbol(ctx.IDENTIFIER().getText()).ifPresent((VariableSymbol s) -> {
        if (notVariableInitialization(ctx, s)) {
          addVariableUsage(
            s.getRootParent(SymbolKind.Method),
            ctx.IDENTIFIER().getText(),
            Ranges.create(ctx.IDENTIFIER()),
            false
          );
        }
      });

      return super.visitForEachStatement(ctx);
    }

    private Optional findVariableSymbol(String variableName) {
      var variableSymbol = documentContext.getSymbolTree()
        .getVariableSymbol(variableName, currentScope);

      if (variableSymbol.isPresent()) {
        return variableSymbol;
      }

      return documentContext.getSymbolTree()
        .getVariableSymbol(variableName, documentContext.getSymbolTree().getModule());
    }

    private boolean notVariableInitialization(BSLParser.LValueContext ctx, VariableSymbol variableSymbol) {
      return !Ranges.containsRange(variableSymbol.getRange(), Ranges.create(ctx));
    }

    private boolean notVariableInitialization(BSLParser.ModuleVarDeclarationContext ctx,
                                              VariableSymbol variableSymbol) {
      return !Ranges.containsRange(variableSymbol.getRange(), Ranges.create(ctx));
    }

    private boolean notVariableInitialization(BSLParser.ForStatementContext ctx, VariableSymbol variableSymbol) {
      return !Ranges.containsRange(variableSymbol.getRange(), Ranges.create(ctx.IDENTIFIER()));
    }

    private boolean notVariableInitialization(BSLParser.ForEachStatementContext ctx, VariableSymbol variableSymbol) {
      return !Ranges.containsRange(variableSymbol.getRange(), Ranges.create(ctx.IDENTIFIER()));
    }

    private void addVariableUsage(Optional methodSymbol,
                                  String variableName,
                                  Range range,
                                  boolean usage) {
      var methodName = "";

      if (methodSymbol.isPresent()) {
        methodName = methodSymbol.get().getName();
      }

      index.addVariableUsage(
        documentContext.getUri(),
        MdoRefBuilder.getMdoRef(documentContext),
        documentContext.getModuleType(),
        methodName,
        variableName,
        range,
        !usage
      );
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy