Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,35 @@ public void addMethodCall(URI uri, String mdoRef, ModuleType moduleType, String
locationRepository.updateLocation(symbolOccurrence);
}

/**
* Добавить ссылку на модуль в индекс.
*
* @param uri URI документа, откуда произошло обращение к модулю.
* @param mdoRef Ссылка на объект-метаданных модуля (например, CommonModule.ОбщийМодуль1).
* @param moduleType Тип модуля (например, {@link ModuleType#CommonModule}).
* @param range Диапазон, в котором происходит обращение к модулю.
*/
public void addModuleReference(URI uri, String mdoRef, ModuleType moduleType, Range range) {
var symbol = Symbol.builder()
.mdoRef(mdoRef)
.moduleType(moduleType)
.scopeName("")
.symbolKind(SymbolKind.Module)
.symbolName("")
.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);
}

/**
* Добавить обращение к переменной в индекс.
*
Expand Down Expand Up @@ -269,6 +298,12 @@ private Optional<SourceDefinedSymbol> getSourceDefinedSymbol(Symbol symbolEntity
.or(() -> symbolTree.getVariableSymbol(symbolName, symbolTree.getModule())));
}

if (symbolEntity.symbolKind() == SymbolKind.Module) {
return serverContext.getDocument(mdoRef, moduleType)
.map(DocumentContext::getSymbolTree)
.map(SymbolTree::getModule);
}

return serverContext.getDocument(mdoRef, moduleType)
.map(DocumentContext::getSymbolTree)
.flatMap(symbolTree -> symbolTree.getMethodSymbol(symbolName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
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.CommonModuleReference;
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.mdclasses.CF;
import com.github._1c_syntax.bsl.parser.BSLParser;
import com.github._1c_syntax.bsl.parser.BSLParserBaseVisitor;
import com.github._1c_syntax.bsl.parser.BSLParserRuleContext;
Expand All @@ -47,7 +49,10 @@

import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -263,6 +268,7 @@ private class VariableSymbolReferenceIndexFinder extends BSLParserBaseVisitor<BS

private final DocumentContext documentContext;
private SourceDefinedSymbol currentScope;
private final Map<String, String> variableToCommonModuleMap = new HashMap<>();

@Override
public BSLParserRuleContext visitModuleVarDeclaration(BSLParser.ModuleVarDeclarationContext ctx) {
Expand All @@ -283,6 +289,10 @@ public BSLParserRuleContext visitModuleVarDeclaration(BSLParser.ModuleVarDeclara
@Override
public BSLParserRuleContext visitSub(BSLParser.SubContext ctx) {
currentScope = documentContext.getSymbolTree().getModule();

// При входе в новый метод очищаем mappings только для локальных переменных.
// Модульные переменные должны сохраняться между методами.
clearLocalVariableMappings();

if (!Trees.nodeContainsErrors(ctx)) {
documentContext
Expand All @@ -296,6 +306,58 @@ public BSLParserRuleContext visitSub(BSLParser.SubContext ctx) {
return result;
}

/**
* Очищает mappings для локальных переменных, сохраняя модульные.
*/
private void clearLocalVariableMappings() {
var moduleSymbolTree = documentContext.getSymbolTree();
var module = moduleSymbolTree.getModule();

// Оставляем только те mappings, которые соответствуют модульным переменным
variableToCommonModuleMap.keySet().removeIf(variableKey -> {
// Ищем переменную на уровне модуля
var moduleVariable = moduleSymbolTree.getVariableSymbol(variableKey, module);
// Если переменной нет на уровне модуля - это локальная переменная, удаляем mapping
return moduleVariable.isEmpty();
});
}

@Override
public BSLParserRuleContext visitAssignment(BSLParser.AssignmentContext ctx) {
// Detect pattern: Variable = ОбщегоНазначения.ОбщийМодуль("ModuleName") or Variable = ОбщийМодуль("ModuleName")
var lValue = ctx.lValue();
var expression = ctx.expression();

if (lValue != null && lValue.IDENTIFIER() != null && expression != null) {
var variableKey = lValue.IDENTIFIER().getText().toLowerCase(Locale.ENGLISH);
if (CommonModuleReference.isCommonModuleExpression(expression)) {
var commonModuleOpt = CommonModuleReference.extractCommonModuleName(expression)
.flatMap(moduleName -> documentContext.getServerContext()
.getConfiguration()
.findCommonModule(moduleName));
if (commonModuleOpt.isPresent()) {
var mdoRef = commonModuleOpt.get().getMdoReference().getMdoRef();
variableToCommonModuleMap.put(variableKey, mdoRef);

index.addModuleReference(
documentContext.getUri(),
mdoRef,
ModuleType.CommonModule,
Ranges.create(expression)
);
} else {
// Модуль не найден - удаляем старый mapping если был
variableToCommonModuleMap.remove(variableKey);
}
} else {
// Переменная переназначена на что-то другое - очищаем mapping
variableToCommonModuleMap.remove(variableKey);
}
}

return super.visitAssignment(ctx);
}

@Override
public BSLParserRuleContext visitLValue(BSLParser.LValueContext ctx) {
if (ctx.IDENTIFIER() == null) {
Expand Down Expand Up @@ -323,6 +385,21 @@ public BSLParserRuleContext visitCallStatement(BSLParser.CallStatementContext ct
}

var variableName = ctx.IDENTIFIER().getText();

// Check if variable references a common module
var commonModuleMdoRef = variableToCommonModuleMap.get(variableName.toLowerCase(Locale.ENGLISH));

if (commonModuleMdoRef != null) {
// Process method calls on the common module variable
// Check both modifiers and accessCall
if (!ctx.modifier().isEmpty()) {
processCommonModuleMethodCalls(ctx.modifier(), commonModuleMdoRef);
}
if (ctx.accessCall() != null) {
processCommonModuleAccessCall(ctx.accessCall(), commonModuleMdoRef);
}
}

findVariableSymbol(variableName)
.ifPresent(s -> addVariableUsage(
s.getRootParent(SymbolKind.Method), variableName, Ranges.create(ctx.IDENTIFIER()), true
Expand All @@ -338,6 +415,27 @@ public BSLParserRuleContext visitComplexIdentifier(BSLParser.ComplexIdentifierCo
}

var variableName = ctx.IDENTIFIER().getText();

// Check if we are inside a callStatement - if so, skip processing here to avoid duplication
var parentCallStatement = Trees.getRootParent(ctx, BSLParser.RULE_callStatement);
var isInsideCallStatement = false;
if (parentCallStatement instanceof BSLParser.CallStatementContext callStmt) {
isInsideCallStatement = callStmt.IDENTIFIER() != null
&& callStmt.IDENTIFIER().getText().equals(variableName);
}

// Check if variable references a common module
var commonModuleMdoRef = variableToCommonModuleMap.get(variableName.toLowerCase(Locale.ENGLISH));
if (commonModuleMdoRef != null && !ctx.modifier().isEmpty() && !isInsideCallStatement) {
// Process method calls on the common module variable
processCommonModuleMethodCalls(ctx.modifier(), commonModuleMdoRef);
}

// Check if this is a manager module reference (e.g., Справочники.ИмяСправочника.Метод())
if (!isInsideCallStatement) {
processManagerModuleReference(ctx);
}

findVariableSymbol(variableName)
.ifPresent(s -> addVariableUsage(
s.getRootParent(SymbolKind.Method), variableName, Ranges.create(ctx.IDENTIFIER()), true
Expand Down Expand Up @@ -435,5 +533,137 @@ private void addVariableUsage(Optional<SourceDefinedSymbol> methodSymbol,
!usage
);
}

private void processCommonModuleMethodCalls(List<? extends BSLParser.ModifierContext> modifiers, String mdoRef) {
for (var modifier : modifiers) {
var accessCall = modifier.accessCall();
if (accessCall != null) {
processCommonModuleAccessCall(accessCall, mdoRef);
}
}
}

private void processCommonModuleAccessCall(BSLParser.AccessCallContext accessCall, String mdoRef) {
var methodCall = accessCall.methodCall();
if (methodCall != null && methodCall.methodName() != null) {
var methodNameToken = methodCall.methodName().IDENTIFIER();
if (methodNameToken != null) {
index.addMethodCall(
documentContext.getUri(),
mdoRef,
ModuleType.CommonModule,
methodNameToken.getText(),
Ranges.create(methodNameToken)
);
}
}
}

/**
* Обрабатывает обращение к модулю менеджера (например, Справочники.ИмяСправочника.Метод()).
* Находит и индексирует вызовы методов менеджера объектов метаданных.
*/
private void processManagerModuleReference(BSLParser.ComplexIdentifierContext ctx) {
// Проверяем, является ли это обращением к модулю менеджера
var parentCtx = Trees.getRootParent(ctx, BSLParser.RULE_expression);
if (!(parentCtx instanceof BSLParser.ExpressionContext expressionCtx)) {
return;
}

var managerInfo = CommonModuleReference.extractManagerModuleInfo(expressionCtx);
if (managerInfo.isEmpty()) {
return;
}

var info = managerInfo.get();
var configuration = documentContext.getServerContext().getConfiguration();

// Ищем объект метаданных по типу менеджера и имени
var mdoRef = findManagerModuleMdoRef(configuration, info.managerType(), info.objectName());
if (mdoRef.isEmpty()) {
return;
}

// Обрабатываем вызовы методов на модуле менеджера
// Модификаторы после первого (имени объекта) могут содержать вызовы методов
var modifiers = ctx.modifier();
if (modifiers.size() > 1) {
// Пропускаем первый модификатор (имя объекта), обрабатываем остальные
for (int i = 1; i < modifiers.size(); i++) {
var modifier = modifiers.get(i);
var accessCall = modifier.accessCall();
if (accessCall != null) {
processManagerModuleAccessCall(accessCall, mdoRef.get());
}
}
}
}

/**
* Находит mdoRef для модуля менеджера по типу и имени объекта.
*/
private Optional<String> findManagerModuleMdoRef(
CF configuration,
String managerType,
String objectName
) {
// Преобразуем тип менеджера в тип объекта метаданных
var mdoTypeName = mapManagerTypeToMdoType(managerType);
if (mdoTypeName.isEmpty()) {
return Optional.empty();
}

// Формируем mdoRef в формате "ТипОбъекта.ИмяОбъекта"
var mdoRef = mdoTypeName.get() + "." + objectName;

// Проверяем, что такой объект существует в конфигурации
var child = configuration.findChild(mdoRef);
if (child.isPresent()) {
return Optional.of(mdoRef);
}

return Optional.empty();
}

/**
* Преобразует тип менеджера в тип объекта метаданных.
*/
private Optional<String> mapManagerTypeToMdoType(String managerType) {
var lowerType = managerType.toLowerCase(Locale.ENGLISH);
return switch (lowerType) {
case "справочники", "catalogs" -> Optional.of("Catalog");
case "документы", "documents" -> Optional.of("Document");
case "регистрысведений", "informationregisters" -> Optional.of("InformationRegister");
case "регистрынакопления", "accumulationregisters" -> Optional.of("AccumulationRegister");
case "регистрыбухгалтерии", "accountingregisters" -> Optional.of("AccountingRegister");
case "регистрырасчета", "calculationregisters" -> Optional.of("CalculationRegister");
case "планывидовхарактеристик", "chartsofcharacteristictypes" -> Optional.of("ChartOfCharacteristicTypes");
case "планысчетов", "chartsofaccounts" -> Optional.of("ChartOfAccounts");
case "планывидоврасчета", "chartsofcalculationtypes" -> Optional.of("ChartOfCalculationTypes");
case "планыобмена", "exchangeplans" -> Optional.of("ExchangePlan");
case "бизнеспроцессы", "businessprocesses" -> Optional.of("BusinessProcess");
case "задачи", "tasks" -> Optional.of("Task");
default -> Optional.empty();
};
}

/**
* Обрабатывает вызов метода на модуле менеджера.
*/
private void processManagerModuleAccessCall(BSLParser.AccessCallContext accessCall, String mdoRef) {
var methodCall = accessCall.methodCall();
if (methodCall != null && methodCall.methodName() != null) {
var methodNameToken = methodCall.methodName().IDENTIFIER();
if (methodNameToken != null) {
index.addMethodCall(
documentContext.getUri(),
mdoRef,
ModuleType.ManagerModule,
methodNameToken.getText(),
Ranges.create(methodNameToken)
);
}
}
}
}
}
Loading
Loading