diff --git a/docs/en/features/ConfigurationFile.md b/docs/en/features/ConfigurationFile.md index d4c3e92268b..45821925549 100644 --- a/docs/en/features/ConfigurationFile.md +++ b/docs/en/features/ConfigurationFile.md @@ -42,6 +42,8 @@ If there is no configuration file, an attempt will be made to find the ".bsl-lan | `traceLog` | `String` | To log all requests *(incoming and outgoing)* between **BSL Language Server** and **Language Client** from used editor/IDE, this parameter sets log file path. The path can set either absolute or relative *(from project root)*, by default the value is not set.

**WARNING**

* When starting **BSL Language Server** overwrites this file
* Speed of interaction between client and server **DRAMATICALLY REDUCED** | | `configurationRoot` | `String` | This parameter is intended to indicate the root directory the 1C configuration files are located in the project directory. It can be useful if there are several configuration directories in the same project directory or when the structure of the project directory is so complex. By default, the parameter is empty and `BSL Language Server` determines the location of the configuration root directory independently | | `sendErrors` | `String` | Mode for sending error messages to BSL Language Server developers. More [Monitoring](Monitoring.md).Possible values:
* `ask` - ask permission on every error *(set by default)*.
* `send` - always send error messages.
* `never` - never send error messages. | +| `references` | `JSON-Object` | Contains reference index settings | +| ⤷   `commonModuleAccessors` | `Array` `String` | List of 'Module.Method' patterns for methods returning common module references (e.g. `CommonUse.CommonModule("ModuleName")`). Supports both format with module name (`CommonUse.CommonModule`) and local call (`CommonModule`). By default, includes standard BSL patterns: `ОбщийМодуль`, `CommonModule`, `ОбщегоНазначения.ОбщийМодуль`, `ОбщегоНазначенияКлиент.ОбщийМодуль`, `ОбщегоНазначенияСервер.ОбщийМодуль`, `ОбщегоНазначенияКлиентСервер.ОбщийМодуль`, `ОбщегоНазначенияПовтИсп.ОбщийМодуль` and their English equivalents. | You can use the following JSON schema to make it easier to compile and edit a configuration file: diff --git a/docs/features/ConfigurationFile.md b/docs/features/ConfigurationFile.md index 2b1caa03135..a34b7934593 100644 --- a/docs/features/ConfigurationFile.md +++ b/docs/features/ConfigurationFile.md @@ -46,6 +46,8 @@ BSL Language Server предоставляет возможность измен | `traceLog` | `Строка` | Для логирования всех запросов *(входящих и исходящих)* между **BSL Language Server** и **Language Client** из используемого редактора/IDE, в этом параметре можно указать путь к файлу лога. Путь можно указывать как абсолютный, так и относительный *(от корня анализируемого проекта)*, по умолчанию значение не заполнено.

**ВНИМАНИЕ**

* При запуске **BSL Language Server** перезаписывает указанный файл
* Скорость взаимодействия между клиентом и сервером **ЗНАЧИТЕЛЬНО ЗАМЕДЛЯЕТСЯ** | | `configurationRoot` | `Строка` | Данный параметр предназначен для указания корневого каталога, в котором находятся файлы конфигурации 1С в каталоге проекта. Может быть полезен в случае нахождения нескольких каталогов конфигураций в одном каталоге проекта либо при сложной структуре каталога проекта. По умолчанию параметр не заполнен и `BSL Language Server` самостоятельно определяет расположение корневого каталога конфигурации | | `sendErrors` | `Строка` | Режим отправки сообщений об ошибках разработчикам BSL Language Server. Подробнее - на странице [Мониторинг и отправка ошибок](Monitoring.md). Возможные значения:
* `ask` - спрашивать разрешение при каждой ошибке *(установлен по умолчанию)*.
* `send` - всегда отправлять сообщения об ошибках.
* `never` - никогда не отправлять сообщения об ошибках. | +| `references` | `JSON-Объект` | Содержит настройки построения индекса ссылок | +| ⤷   `commonModuleAccessors` | `Массив` `Строка` | Список паттернов "Модуль.Метод" для методов, возвращающих ссылку на общий модуль (например, `ОбщегоНазначения.ОбщийМодуль("ИмяМодуля")`). Поддерживается как формат с указанием модуля (`ОбщегоНазначения.ОбщийМодуль`), так и локальный вызов (`ОбщийМодуль`). По умолчанию включает стандартные варианты из БСП: `ОбщийМодуль`, `CommonModule`, `ОбщегоНазначения.ОбщийМодуль`, `ОбщегоНазначенияКлиент.ОбщийМодуль`, `ОбщегоНазначенияСервер.ОбщийМодуль`, `ОбщегоНазначенияКлиентСервер.ОбщийМодуль`, `ОбщегоНазначенияПовтИсп.ОбщийМодуль` и их английские эквиваленты. | Для облегчения составления и редактирования конфигурационного файла можно использовать следующую JSON-схему: diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfiguration.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfiguration.java index 9ce6ec38c28..1e141c50a87 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfiguration.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfiguration.java @@ -32,6 +32,7 @@ import com.github._1c_syntax.bsl.languageserver.configuration.documentlink.DocumentLinkOptions; import com.github._1c_syntax.bsl.languageserver.configuration.formating.FormattingOptions; import com.github._1c_syntax.bsl.languageserver.configuration.inlayhints.InlayHintOptions; +import com.github._1c_syntax.bsl.languageserver.configuration.references.ReferencesOptions; import com.github._1c_syntax.utils.Absolute; import org.jspecify.annotations.Nullable; import jakarta.annotation.PostConstruct; @@ -97,6 +98,10 @@ public class LanguageServerConfiguration { @Setter(value = AccessLevel.NONE) private FormattingOptions formattingOptions = new FormattingOptions(); + @JsonProperty("references") + @Setter(value = AccessLevel.NONE) + private ReferencesOptions referencesOptions = new ReferencesOptions(); + private String siteRoot = "https://1c-syntax.github.io/bsl-language-server"; private boolean useDevSite; @@ -211,5 +216,6 @@ private void copyPropertiesFrom(LanguageServerConfiguration configuration) { PropertyUtils.copyProperties(this.diagnosticsOptions, configuration.diagnosticsOptions); PropertyUtils.copyProperties(this.documentLinkOptions, configuration.documentLinkOptions); PropertyUtils.copyProperties(this.formattingOptions, configuration.formattingOptions); + PropertyUtils.copyProperties(this.referencesOptions, configuration.referencesOptions); } } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/references/ReferencesOptions.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/references/ReferencesOptions.java new file mode 100644 index 00000000000..f63b7d11dfa --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/references/ReferencesOptions.java @@ -0,0 +1,74 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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.configuration.references; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Настройки для построения индекса ссылок. + *

+ * Позволяет указать список модулей и методов, возвращающих ссылку на общий модуль + * (например, ОбщегоНазначения.ОбщийМодуль("ИмяМодуля")). + */ +@Data +@AllArgsConstructor(onConstructor = @__({@JsonCreator(mode = JsonCreator.Mode.DISABLED)})) +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class ReferencesOptions { + + /** + * Список паттернов "Модуль.Метод" для методов, возвращающих ссылку на общий модуль. + *

+ * Формат: "ИмяМодуля.ИмяМетода", например: + *

+ *

+ * По умолчанию включает стандартные варианты из БСП. + */ + private List commonModuleAccessors = new ArrayList<>(List.of( + // Локальный вызов + "ОбщийМодуль", + "CommonModule", + // Стандартные модули БСП + "ОбщегоНазначения.ОбщийМодуль", + "ОбщегоНазначенияКлиент.ОбщийМодуль", + "ОбщегоНазначенияСервер.ОбщийМодуль", + "ОбщегоНазначенияКлиентСервер.ОбщийМодуль", + "ОбщегоНазначенияПовтИсп.ОбщийМодуль", + // Английские варианты + "CommonUse.CommonModule", + "CommonUseClient.CommonModule", + "CommonUseServer.CommonModule", + "CommonUseClientServer.CommonModule" + )); +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/references/package-info.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/references/package-info.java new file mode 100644 index 00000000000..676c5f5dba7 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/references/package-info.java @@ -0,0 +1,28 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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. + */ +/** + * Пакет содержит настройки для построения индекса ссылок. + */ +@NullMarked +package com.github._1c_syntax.bsl.languageserver.configuration.references; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter.java index 69bc8ef339d..a0ca7ca79c2 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter.java @@ -23,6 +23,7 @@ import com.github._1c_syntax.bsl.languageserver.context.symbol.AnnotationSymbol; import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.ModuleSymbol; import com.github._1c_syntax.bsl.languageserver.context.symbol.ParameterDefinition; import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol; import com.github._1c_syntax.bsl.languageserver.context.symbol.description.MethodDescription; @@ -137,6 +138,23 @@ public String getSectionWithCodeFences(Collection codeBlocks, String res return codeFences; } + public String getLocation(ModuleSymbol symbol) { + var documentContext = symbol.getOwner(); + var uri = documentContext.getUri(); + + var mdObject = documentContext.getMdObject(); + String mdoRefLocal = mdObject.map(md -> documentContext.getServerContext() + .getConfiguration() + .getMdoRefLocal(md) + ).orElseGet(documentContext::getMdoRef); + + return String.format( + "[%s](%s)", + mdoRefLocal, + uri + ); + } + public String getLocation(MethodSymbol symbol) { var documentContext = symbol.getOwner(); var startPosition = symbol.getSelectionRange().getStart(); diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder.java new file mode 100644 index 00000000000..d1463a028ce --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder.java @@ -0,0 +1,139 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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.hover; + +import com.github._1c_syntax.bsl.languageserver.context.symbol.ModuleSymbol; +import com.github._1c_syntax.bsl.languageserver.utils.Resources; +import com.github._1c_syntax.bsl.mdo.CommonModule; +import lombok.RequiredArgsConstructor; +import org.eclipse.lsp4j.MarkupContent; +import org.eclipse.lsp4j.MarkupKind; +import org.eclipse.lsp4j.SymbolKind; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.StringJoiner; + +/** + * Построитель контента для всплывающего окна для {@link ModuleSymbol}. + */ +@Component +@RequiredArgsConstructor +public class ModuleSymbolMarkupContentBuilder implements MarkupContentBuilder { + + private final Resources resources; + private final DescriptionFormatter descriptionFormatter; + + @Override + public MarkupContent getContent(ModuleSymbol symbol) { + var markupBuilder = new StringJoiner("\n"); + + // Местоположение модуля + String moduleLocation = descriptionFormatter.getLocation(symbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, moduleLocation); + + // Информация о модуле из метаданных + String moduleInfo = getModuleInfo(symbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, moduleInfo); + + String content = markupBuilder.toString(); + return new MarkupContent(MarkupKind.MARKDOWN, content); + } + + @Override + public SymbolKind getSymbolKind() { + return SymbolKind.Module; + } + + private String getModuleInfo(ModuleSymbol symbol) { + var documentContext = symbol.getOwner(); + var mdObject = documentContext.getMdObject(); + + if (mdObject.isEmpty()) { + return ""; + } + + var mdo = mdObject.get(); + if (!(mdo instanceof CommonModule commonModule)) { + return ""; + } + + var moduleInfoBuilder = new StringJoiner("\n"); + + // Комментарий + var comment = commonModule.getComment(); + if (!comment.isBlank()) { + moduleInfoBuilder.add(comment); + moduleInfoBuilder.add(""); + } + + // Флаги доступности + var flags = new ArrayList(); + + if (commonModule.isServer()) { + flags.add(getResourceString("server")); + } + if (commonModule.isClientManagedApplication()) { + flags.add(getResourceString("clientManagedApplication")); + } + if (commonModule.isClientOrdinaryApplication()) { + flags.add(getResourceString("clientOrdinaryApplication")); + } + if (commonModule.isExternalConnection()) { + flags.add(getResourceString("externalConnection")); + } + if (commonModule.isServerCall()) { + flags.add(getResourceString("serverCall")); + } + if (commonModule.isPrivileged()) { + flags.add(getResourceString("privilegedMode")); + } + if (commonModule.isGlobal()) { + flags.add(getResourceString("global")); + } + + if (!flags.isEmpty()) { + var flagsHeader = "**" + getResourceString("availability") + ":** "; + moduleInfoBuilder.add(flagsHeader + String.join(", ", flags)); + moduleInfoBuilder.add(""); + } + + // Режим повторного использования + var returnValueReuse = commonModule.getReturnValuesReuse(); + var reuseKey = switch (returnValueReuse) { + case DURING_REQUEST -> "duringRequest"; + case DURING_SESSION -> "duringSession"; + case DONT_USE, UNKNOWN -> ""; + }; + + if (!reuseKey.isEmpty()) { + var reuseHeader = "**" + getResourceString("returnValuesReuse") + ":** "; + moduleInfoBuilder.add(reuseHeader + getResourceString(reuseKey)); + } + + return moduleInfoBuilder.toString(); + } + + private String getResourceString(String key) { + return resources.getResourceString(getClass(), key); + } +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndex.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndex.java index 3987fd57513..521c5707b76 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndex.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndex.java @@ -195,6 +195,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); + } + /** * Добавить обращение к переменной в индекс. * @@ -266,6 +295,12 @@ private Optional 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)); diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java index b9fea5f235f..0dbcd7e6e22 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java @@ -21,6 +21,7 @@ */ package com.github._1c_syntax.bsl.languageserver.references; +import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration; 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.events.ServerContextDocumentRemovedEvent; @@ -28,6 +29,7 @@ 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.ModuleReference; 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; @@ -37,19 +39,23 @@ import com.github._1c_syntax.bsl.parser.BSLParser; import com.github._1c_syntax.bsl.parser.BSLParserBaseVisitor; import com.github._1c_syntax.bsl.types.ModuleType; -import org.jspecify.annotations.Nullable; import lombok.RequiredArgsConstructor; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SymbolKind; +import org.jspecify.annotations.Nullable; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; 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; @@ -73,6 +79,7 @@ public class ReferenceIndexFiller { ); private final ReferenceIndex index; + private final LanguageServerConfiguration languageServerConfiguration; @EventListener public void handleEvent(DocumentContextContentChangedEvent event) { @@ -133,6 +140,9 @@ public ParserRuleContext visitCallStatement(BSLParser.CallStatementContext ctx) return super.visitCallStatement(ctx); } + // Добавляем ссылку на модуль по позиции идентификатора (только для общих модулей) + addModuleReferenceForCommonModuleIdentifier(ctx.IDENTIFIER()); + Methods.getMethodName(ctx).ifPresent(methodName -> checkCall(mdoRef, methodName)); return super.visitCallStatement(ctx); @@ -145,6 +155,9 @@ public ParserRuleContext visitComplexIdentifier(BSLParser.ComplexIdentifierConte return super.visitComplexIdentifier(ctx); } + // Добавляем ссылку на модуль по позиции идентификатора (только для общих модулей) + addModuleReferenceForCommonModuleIdentifier(ctx.IDENTIFIER()); + Methods.getMethodName(ctx).ifPresent(methodName -> checkCall(mdoRef, methodName)); return super.visitComplexIdentifier(ctx); } @@ -219,6 +232,31 @@ private void checkCall(String mdoRef, Token methodName) { } } + /** + * Добавляет ссылку на модуль по позиции идентификатора, только если идентификатор является + * именем общего модуля. Для вызовов вида Справочники.Имя.Метод() ссылка не добавляется, + * так как "Справочники" - это тип MDO, а не имя модуля. + */ + private void addModuleReferenceForCommonModuleIdentifier(@Nullable TerminalNode identifier) { + if (identifier == null) { + return; + } + + var identifierText = identifier.getText(); + + documentContext.getServerContext() + .getConfiguration() + .findCommonModule(identifierText) + .ifPresent(commonModule -> { + index.addModuleReference( + documentContext.getUri(), + commonModule.getMdoReference().getMdoRef(), + ModuleType.CommonModule, + Ranges.create(identifier) + ); + }); + } + private void addMethodCall(String mdoRef, ModuleType moduleType, String methodName, Range range) { index.addMethodCall(documentContext.getUri(), mdoRef, moduleType, methodName, range); } @@ -273,11 +311,20 @@ private Set calcParams(BSLParser.@Nullable ParamListContext paramList) { } } - @RequiredArgsConstructor private class VariableSymbolReferenceIndexFinder extends BSLParserBaseVisitor { private final DocumentContext documentContext; + private final ModuleReference.ParsedAccessors parsedAccessors; + @SuppressWarnings("NullAway.Init") private SourceDefinedSymbol currentScope; + private final Map variableToCommonModuleMap = new HashMap<>(); + + private VariableSymbolReferenceIndexFinder(DocumentContext documentContext) { + this.documentContext = documentContext; + this.parsedAccessors = ModuleReference.parseAccessors( + languageServerConfiguration.getReferencesOptions().getCommonModuleAccessors() + ); + } @Override public ParserRuleContext visitModuleVarDeclaration(BSLParser.ModuleVarDeclarationContext ctx) { @@ -299,6 +346,10 @@ public ParserRuleContext visitModuleVarDeclaration(BSLParser.ModuleVarDeclaratio public ParserRuleContext visitSub(BSLParser.SubContext ctx) { currentScope = documentContext.getSymbolTree().getModule(); + // При входе в новый метод очищаем mappings только для локальных переменных. + // Модульные переменные должны сохраняться между методами. + clearLocalVariableMappings(); + if (!Trees.nodeContainsErrors(ctx)) { documentContext .getSymbolTree() @@ -311,6 +362,58 @@ public ParserRuleContext visitSub(BSLParser.SubContext ctx) { return result; } + /** + * Очищает mappings для локальных переменных, сохраняя модульные. + */ + private void clearLocalVariableMappings() { + var moduleSymbolTree = documentContext.getSymbolTree(); + var module = moduleSymbolTree.getModule(); + + // Оставляем только те mappings, которые соответствуют модульным переменным + variableToCommonModuleMap.keySet().removeIf((String variableKey) -> { + // Ищем переменную на уровне модуля + var moduleVariable = moduleSymbolTree.getVariableSymbol(variableKey, module); + // Если переменной нет на уровне модуля - это локальная переменная, удаляем mapping + return moduleVariable.isEmpty(); + }); + } + + @Override + public ParserRuleContext 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 (ModuleReference.isCommonModuleExpression(expression, parsedAccessors)) { + var commonModuleOpt = ModuleReference.extractCommonModuleName(expression, parsedAccessors) + .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 ParserRuleContext visitLValue(BSLParser.LValueContext ctx) { if (ctx.IDENTIFIER() == null) { @@ -338,6 +441,21 @@ public ParserRuleContext visitCallStatement(BSLParser.CallStatementContext ctx) } 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 @@ -353,6 +471,22 @@ public ParserRuleContext visitComplexIdentifier(BSLParser.ComplexIdentifierConte } 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().equalsIgnoreCase(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); + } + findVariableSymbol(variableName) .ifPresent(s -> addVariableUsage( s.getRootParent(SymbolKind.Method), variableName, Ranges.create(ctx.IDENTIFIER()), true @@ -450,5 +584,30 @@ private void addVariableUsage(Optional methodSymbol, !usage ); } + + private void processCommonModuleMethodCalls(List 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) + ); + } + } + } } } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/ModuleReference.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/ModuleReference.java new file mode 100644 index 00000000000..16ba543efd0 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/ModuleReference.java @@ -0,0 +1,295 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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.utils; + +import com.github._1c_syntax.bsl.parser.BSLParser; +import lombok.experimental.UtilityClass; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.jspecify.annotations.Nullable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Утилитный класс для работы со ссылками на общие модули. + *

+ * Предоставляет методы для анализа конструкций получения ссылки на общий модуль + * через ОбщегоНазначения.ОбщийМодуль("ИмяМодуля"), ОбщегоНазначенияКлиент.ОбщийМодуль("ИмяМодуля") + * и других вариантов. + */ +@UtilityClass +public class ModuleReference { + + /** + * Предварительно разобранные паттерны доступа к общим модулям. + *

+ * Структура: + * - localMethods: Set методов для локального вызова (без модуля) + * - moduleMethodPairs: Map из имени модуля -> Set методов этого модуля + */ + public record ParsedAccessors( + Set localMethods, + Map> moduleMethodPairs + ) {} + + /** + * Разбирает список паттернов доступа к общим модулям один раз. + *

+ * Вызывается один раз при инициализации и результат кэшируется. + * + * @param commonModuleAccessors Список паттернов "Модуль.Метод" или "Метод" для локального вызова + * @return Предварительно разобранные паттерны + */ + public static ParsedAccessors parseAccessors(List commonModuleAccessors) { + var localMethods = new HashSet(); + var moduleMethodPairs = new HashMap>(); + + for (var pattern : commonModuleAccessors) { + var patternLower = pattern.toLowerCase(Locale.ENGLISH); + + if (patternLower.contains(".")) { + var parts = patternLower.split("\\.", 2); + if (parts.length == 2) { + moduleMethodPairs + .computeIfAbsent(parts[0], k -> new HashSet<>()) + .add(parts[1]); + } + } else { + localMethods.add(patternLower); + } + } + + return new ParsedAccessors(localMethods, moduleMethodPairs); + } + + /** + * Проверить, является ли expression вызовом получения ссылки на общий модуль. + * Использует предварительно разобранные паттерны. + * + * @param expression Контекст выражения + * @param parsedAccessors Предварительно разобранные паттерны + * @return true, если это вызов метода получения общего модуля + */ + public static boolean isCommonModuleExpression( + BSLParser.ExpressionContext expression, + ParsedAccessors parsedAccessors + ) { + + var members = expression.member(); + if (members.isEmpty()) { + return false; + } + + for (var member : members) { + if (isCommonModuleExpressionMember(member, parsedAccessors)) { + return true; + } + } + + return false; + } + + /** + * Извлечь имя общего модуля из expression. + * + * @param expression Контекст выражения + * @param parsedAccessors Предварительно разобранные паттерны + * @return Имя модуля, если удалось извлечь + */ + public static Optional extractCommonModuleName( + BSLParser.ExpressionContext expression, + ParsedAccessors parsedAccessors + ) { + + var members = expression.member(); + if (members.isEmpty()) { + return Optional.empty(); + } + + for (var member : members) { + var result = extractCommonModuleNameFromMember(member, parsedAccessors); + if (result.isPresent()) { + return result; + } + } + + return Optional.empty(); + } + + // ===== Private helper methods ===== + + private static boolean isCommonModuleExpressionMember( + BSLParser.MemberContext member, + ParsedAccessors parsedAccessors + ) { + // Случай 1: IDENTIFIER - ОбщийМодуль("Name") + if (member.IDENTIFIER() != null) { + var identifier = member.IDENTIFIER().getText(); + if (isLocalMethodMatch(identifier, parsedAccessors)) { + return true; + } + } + + // Случай 2: complexIdentifier с модификаторами + var complexId = member.complexIdentifier(); + if (complexId == null) { + return false; + } + + var identifier = complexId.IDENTIFIER(); + if (identifier == null) { + return false; + } + + var idText = identifier.getText(); + + // Случай 2a: Локальный вызов - ОбщийМодуль("Name") + if (isLocalMethodMatch(idText, parsedAccessors)) { + return true; + } + + // Случай 2b: Модуль.Метод - ОбщегоНазначения.ОбщийМодуль("Name") + return hasMatchingModifierMethodCall(complexId, idText, parsedAccessors); + } + + private static boolean hasMatchingModifierMethodCall( + BSLParser.ComplexIdentifierContext complexId, + String moduleName, + ParsedAccessors parsedAccessors + ) { + return complexId.modifier().stream() + .flatMap(modifier -> extractMethodNameFromModifier(modifier).stream()) + .anyMatch(methodName -> isModuleMethodMatch(methodName, moduleName, parsedAccessors)); + } + + private static Optional extractMethodNameFromModifier(BSLParser.ModifierContext modifier) { + return Optional.ofNullable(modifier.accessCall()) + .map(BSLParser.AccessCallContext::methodCall) + .map(BSLParser.MethodCallContext::methodName) + .map(BSLParser.MethodNameContext::IDENTIFIER) + .map(TerminalNode::getText); + } + + private static Optional extractCommonModuleNameFromMember( + BSLParser.MemberContext member, + ParsedAccessors parsedAccessors + ) { + var complexId = member.complexIdentifier(); + if (complexId == null) { + return Optional.empty(); + } + + var identifier = complexId.IDENTIFIER(); + if (identifier != null) { + var idText = identifier.getText(); + + // Случай 1: Локальный вызов - ОбщийМодуль("Name") + if (isLocalMethodMatch(idText, parsedAccessors)) { + return extractModuleNameFromModifiers(complexId.modifier()); + } + + // Случай 2: Модуль.Метод - ОбщегоНазначения.ОбщийМодуль("Name") + var result = extractModuleNameFromMatchingModifier(complexId, idText, parsedAccessors); + if (result.isPresent()) { + return result; + } + } + + // Случай 3: globalMethodCall внутри complexIdentifier + return extractModuleNameFromGlobalMethodCall(complexId, parsedAccessors); + } + + private static Optional extractModuleNameFromMatchingModifier( + BSLParser.ComplexIdentifierContext complexId, + String moduleName, + ParsedAccessors parsedAccessors + ) { + return complexId.modifier().stream() + .filter(modifier -> extractMethodNameFromModifier(modifier) + .filter(methodName -> isModuleMethodMatch(methodName, moduleName, parsedAccessors)) + .isPresent()) + .findFirst() + .flatMap(modifier -> Optional.ofNullable(modifier.accessCall()) + .map(BSLParser.AccessCallContext::methodCall) + .map(BSLParser.MethodCallContext::doCall) + .flatMap(ModuleReference::extractParameterFromDoCall)); + } + + private static Optional extractModuleNameFromGlobalMethodCall( + BSLParser.ComplexIdentifierContext complexId, + ParsedAccessors parsedAccessors + ) { + var globalMethodCall = complexId.globalMethodCall(); + if (globalMethodCall == null || globalMethodCall.methodName() == null) { + return Optional.empty(); + } + + var methodName = globalMethodCall.methodName().IDENTIFIER(); + if (methodName != null && isLocalMethodMatch(methodName.getText(), parsedAccessors)) { + return extractParameterFromDoCall(globalMethodCall.doCall()); + } + return Optional.empty(); + } + + private static boolean isLocalMethodMatch(String methodName, ParsedAccessors parsedAccessors) { + return parsedAccessors.localMethods().contains(methodName.toLowerCase(Locale.ENGLISH)); + } + + private static boolean isModuleMethodMatch(String methodName, String moduleName, ParsedAccessors parsedAccessors) { + var moduleMethods = parsedAccessors.moduleMethodPairs().get(moduleName.toLowerCase(Locale.ENGLISH)); + return moduleMethods != null && moduleMethods.contains(methodName.toLowerCase(Locale.ENGLISH)); + } + + private static Optional extractModuleNameFromModifiers( + List modifiers + ) { + for (var modifier : modifiers) { + var moduleName = extractParameterFromDoCall(modifier.accessCall()); + if (moduleName.isPresent()) { + return moduleName; + } + } + return Optional.empty(); + } + + private static Optional extractParameterFromDoCall(BSLParser.@Nullable AccessCallContext accessCall) { + return Optional.ofNullable(accessCall) + .map(BSLParser.AccessCallContext::methodCall) + .map(BSLParser.MethodCallContext::doCall) + .flatMap(ModuleReference::extractParameterFromDoCall); + } + + private static Optional extractParameterFromDoCall(BSLParser.@Nullable DoCallContext doCall) { + return Optional.ofNullable(doCall) + .map(BSLParser.DoCallContext::callParamList) + .map(BSLParser.CallParamListContext::callParam) + .filter(params -> !params.isEmpty()) + .map(params -> params.get(0)) + .map(BSLParser.CallParamContext::getText) + .map(Strings::trimQuotes); + } +} diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json index a120d11dc7c..10f52879f8d 100644 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json @@ -1121,6 +1121,34 @@ ], "title": "Send errors and exceptions to developers of BSL Language Server.", "default": "ask" + }, + "references": { + "$id": "#/properties/references", + "type": "object", + "title": "Reference index configuration.", + "properties": { + "commonModuleAccessors": { + "$id": "#/properties/references/commonModuleAccessors", + "type": "array", + "title": "List of 'Module.Method' patterns for methods returning common module references (e.g. CommonUse.CommonModule(\"ModuleName\")).", + "items": { + "type": "string" + }, + "default": [ + "ОбщийМодуль", + "CommonModule", + "ОбщегоНазначения.ОбщийМодуль", + "ОбщегоНазначенияКлиент.ОбщийМодуль", + "ОбщегоНазначенияСервер.ОбщийМодуль", + "ОбщегоНазначенияКлиентСервер.ОбщийМодуль", + "ОбщегоНазначенияПовтИсп.ОбщийМодуль", + "CommonUse.CommonModule", + "CommonUseClient.CommonModule", + "CommonUseServer.CommonModule", + "CommonUseClientServer.CommonModule" + ] + } + } } } } \ No newline at end of file diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder_en.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder_en.properties new file mode 100644 index 00000000000..46224d27e3b --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder_en.properties @@ -0,0 +1,14 @@ +# Availability flags +availability=Availability +server=Server +clientManagedApplication=Client (Managed Application) +clientOrdinaryApplication=Client (Ordinary Application) +externalConnection=External Connection +serverCall=Server Call +privilegedMode=Privileged Mode +global=Global + +# Return values reuse +returnValuesReuse=Return Values Reuse +duringRequest=During Request +duringSession=During Session diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder_ru.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder_ru.properties new file mode 100644 index 00000000000..97d53be47f4 --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilder_ru.properties @@ -0,0 +1,14 @@ +# Availability flags +availability=Доступность +server=Сервер +clientManagedApplication=Клиент (Управляемое приложение) +clientOrdinaryApplication=Клиент (Обычное приложение) +externalConnection=Внешнее соединение +serverCall=Вызов сервера +privilegedMode=Привилегированный режим +global=Глобальный + +# Return values reuse +returnValuesReuse=Повторное использование возвращаемых значений +duringRequest=На время вызова +duringSession=На время сеанса diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilderTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilderTest.java new file mode 100644 index 00000000000..4a88d20ec63 --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/ModuleSymbolMarkupContentBuilderTest.java @@ -0,0 +1,135 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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.hover; + +import com.github._1c_syntax.bsl.languageserver.context.ServerContext; +import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterClass; +import com.github._1c_syntax.bsl.types.ModuleType; +import jakarta.annotation.PostConstruct; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.nio.file.Paths; +import java.util.Arrays; + +import static com.github._1c_syntax.bsl.languageserver.util.TestUtils.PATH_TO_METADATA; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@CleanupContextBeforeClassAndAfterClass +class ModuleSymbolMarkupContentBuilderTest { + + @Autowired + private ModuleSymbolMarkupContentBuilder markupContentBuilder; + + @Autowired + private ServerContext serverContext; + + @PostConstruct + void prepareServerContext() { + serverContext.setConfigurationRoot(Paths.get(PATH_TO_METADATA)); + serverContext.populateContext(); + } + + @Test + void testContentFromCommonModule() { + // given + var documentContext = serverContext.getDocument("CommonModule.ПервыйОбщийМодуль", ModuleType.CommonModule).orElseThrow(); + var moduleSymbol = documentContext.getSymbolTree().getModule(); + + // when + var content = markupContentBuilder.getContent(moduleSymbol).getValue(); + + // then + assertThat(content).isNotEmpty(); + + var blocks = Arrays.asList(content.split("---\n?")); + + // Должны быть: местоположение, информация о модуле + assertThat(blocks).hasSizeGreaterThanOrEqualTo(2); + + // Сигнатура - для CommonModule показывается только имя модуля + assertThat(blocks.get(0)).contains("ОбщийМодуль.ПервыйОбщийМодуль"); + + // Местоположение - используется локализованный mdoRef + assertThat(blocks.get(1)).contains("Доступность:"); + } + + @Test + void testContentFromManagerModule() { + // given + var documentContext = serverContext.getDocument("Catalog.Справочник1", ModuleType.ManagerModule).orElseThrow(); + var moduleSymbol = documentContext.getSymbolTree().getModule(); + + // when + var content = markupContentBuilder.getContent(moduleSymbol).getValue(); + + // then + assertThat(content).isNotEmpty(); + + var blocks = Arrays.asList(content.split("---\n?")); + + assertThat(blocks).hasSizeGreaterThanOrEqualTo(1); + + // Для ManagerModule используется локализованный mdoRef + assertThat(blocks.get(0)).contains("Справочник.Справочник1"); + } + + @Test + void testContentFromObjectModule() { + // given + var documentContext = serverContext.getDocument("Catalog.Справочник1", ModuleType.ObjectModule).orElseThrow(); + var moduleSymbol = documentContext.getSymbolTree().getModule(); + + // when + var content = markupContentBuilder.getContent(moduleSymbol).getValue(); + + // then + assertThat(content).isNotEmpty(); + + var blocks = Arrays.asList(content.split("---\n?")); + + assertThat(blocks).hasSizeGreaterThanOrEqualTo(1); + + // Для ObjectModule используется локализованный mdoRef + assertThat(blocks.get(0)).contains("Справочник.Справочник1"); + } + + @Test + void testCommonModuleWithMetadataInfo() { + // given + var documentContext = serverContext.getDocument("CommonModule.ПервыйОбщийМодуль", ModuleType.CommonModule).orElseThrow(); + var moduleSymbol = documentContext.getSymbolTree().getModule(); + + // when + var content = markupContentBuilder.getContent(moduleSymbol).getValue(); + + // then + assertThat(content).isNotEmpty(); + + // Проверяем, что контент содержит секции с информацией о модуле + // (флаги доступности, режим повторного использования) + // Конкретные значения зависят от тестовых метаданных + assertThat(content).contains("---"); + } +} diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/DefinitionProviderTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/DefinitionProviderTest.java index d7c3dd7da16..a93b5936117 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/DefinitionProviderTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/DefinitionProviderTest.java @@ -49,6 +49,7 @@ class DefinitionProviderTest { private ServerContext serverContext; private static final String PATH_TO_FILE = "./src/test/resources/providers/definition.bsl"; + private static final String PATH_TO_COMMON_MODULE_FILE = "./src/test/resources/providers/definitionCommonModule.bsl"; @PostConstruct void prepareServerContext() { @@ -93,7 +94,7 @@ void testDefinitionOfLocalMethod() { } @Test - void testDefinitionOfCommonModule() { + void testDefinitionOfManagerModuleMethod() { var documentContext = TestUtils.getDocumentContextFromFile(PATH_TO_FILE); var managerModule = serverContext.getDocument("Catalog.Справочник1", ModuleType.ManagerModule).orElseThrow(); var methodSymbol = managerModule.getSymbolTree().getMethodSymbol("ТестЭкспортная").orElseThrow(); @@ -114,4 +115,31 @@ void testDefinitionOfCommonModule() { assertThat(definition.getTargetRange()).isEqualTo(methodSymbol.getRange()); assertThat(definition.getOriginSelectionRange()).isEqualTo(Ranges.create(6, 24, 38)); } + + @Test + void testDefinitionOfCommonModuleName() { + // Тест: клик на "ПервыйОбщийМодуль" в "ПервыйОбщийМодуль.НеУстаревшаяПроцедура()" + // должен вести к модулю ПервыйОбщийМодуль + var documentContext = TestUtils.getDocumentContextFromFile(PATH_TO_COMMON_MODULE_FILE); + var commonModule = serverContext.getDocument("CommonModule.ПервыйОбщийМодуль", ModuleType.CommonModule).orElseThrow(); + var moduleSymbol = commonModule.getSymbolTree().getModule(); + + var params = new DefinitionParams(); + // Position on "ПервыйОбщийМодуль" (line 2, columns 0-17) + params.setPosition(new Position(1, 5)); + + // when + var definitions = definitionProvider.getDefinition(documentContext, params); + + // then + assertThat(definitions).hasSize(1); + + var definition = definitions.get(0); + + assertThat(definition.getTargetUri()).isEqualTo(commonModule.getUri().toString()); + assertThat(definition.getTargetSelectionRange()).isEqualTo(moduleSymbol.getSelectionRange()); + assertThat(definition.getTargetRange()).isEqualTo(moduleSymbol.getRange()); + // "ПервыйОбщийМодуль" spans 17 characters + assertThat(definition.getOriginSelectionRange()).isEqualTo(Ranges.create(1, 0, 17)); + } } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFillerTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFillerTest.java index 6edac53faa0..7d9c3dbda48 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFillerTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFillerTest.java @@ -288,6 +288,173 @@ void testRebuildClearReferences() { assertThat(referencesTo).hasSize(1); } + @Test + void testFindCommonModuleVariableReferences() throws IOException { + var path = Absolute.path("src/test/resources/metadata/designer"); + serverContext.setConfigurationRoot(path); + + var documentContext = TestUtils.getDocumentContextFromFile( + "./src/test/resources/references/ReferenceIndexCommonModuleVariable.bsl" + ); + + // Load the common module that will be referenced + var file = new File("src/test/resources/metadata/designer", + "CommonModules/ПервыйОбщийМодуль/Ext/Module.bsl"); + var uri = Absolute.uri(file); + var commonModuleContext = TestUtils.getDocumentContext( + uri, + FileUtils.readFileToString(file, StandardCharsets.UTF_8), + serverContext + ); + + referenceIndexFiller.fill(documentContext); + + // Check that exported methods from common module are referenced + var procMethod = commonModuleContext.getSymbolTree().getMethodSymbol("НеУстаревшаяПроцедура"); + assertThat(procMethod).isPresent(); + var referencesToProc = referenceIndex.getReferencesTo(procMethod.get()); + // Filter to only references from our test document + var referencesToProcFromTest = referencesToProc.stream() + .filter(ref -> ref.getUri().equals(documentContext.getUri())) + .toList(); + assertThat(referencesToProcFromTest).hasSize(1); + + var funcMethod = commonModuleContext.getSymbolTree().getMethodSymbol("НеУстаревшаяФункция"); + assertThat(funcMethod).isPresent(); + var referencesToFunc = referenceIndex.getReferencesTo(funcMethod.get()); + // Filter to only references from our test document + var referencesToFuncFromTest = referencesToFunc.stream() + .filter(ref -> ref.getUri().equals(documentContext.getUri())) + .toList(); + // Должно быть 2 вызова: в assignment и в условии + assertThat(referencesToFuncFromTest).hasSize(2); + } + + @Test + void testCommonModuleVariableReassignment() throws IOException { + var path = Absolute.path("src/test/resources/metadata/designer"); + serverContext.setConfigurationRoot(path); + + var documentContext = TestUtils.getDocumentContextFromFile( + "./src/test/resources/references/ReferenceIndexCommonModuleReassignment.bsl" + ); + + // Load the common module that will be referenced + var file = new File("src/test/resources/metadata/designer", + "CommonModules/ПервыйОбщийМодуль/Ext/Module.bsl"); + var uri = Absolute.uri(file); + var commonModuleContext = TestUtils.getDocumentContext( + uri, + FileUtils.readFileToString(file, StandardCharsets.UTF_8), + serverContext + ); + + referenceIndexFiller.fill(documentContext); + + // В первой процедуре должна быть только одна ссылка на НеУстаревшаяПроцедура + // (до переназначения переменной на Неопределено) + var procMethod = commonModuleContext.getSymbolTree().getMethodSymbol("НеУстаревшаяПроцедура"); + assertThat(procMethod).isPresent(); + var referencesToProc = referenceIndex.getReferencesTo(procMethod.get()); + var referencesToProcFromTest = referencesToProc.stream() + .filter(ref -> ref.getUri().equals(documentContext.getUri())) + .toList(); + // Должно быть 2 вызова: по одному из каждой процедуры (до переназначения) + assertThat(referencesToProcFromTest).hasSize(2); + + // НеУстаревшаяФункция не должна индексироваться после переназначения на Неопределено + var funcMethod = commonModuleContext.getSymbolTree().getMethodSymbol("НеУстаревшаяФункция"); + assertThat(funcMethod).isPresent(); + var referencesToFunc = referenceIndex.getReferencesTo(funcMethod.get()); + var referencesToFuncFromTest = referencesToFunc.stream() + .filter(ref -> ref.getUri().equals(documentContext.getUri())) + .toList(); + // Не должно быть ссылок, так как вызов после переназначения на Неопределено + assertThat(referencesToFuncFromTest).hasSize(0); + } + + @Test + void testCommonModuleModuleLevelVariable() throws IOException { + var path = Absolute.path("src/test/resources/metadata/designer"); + serverContext.setConfigurationRoot(path); + + var documentContext = TestUtils.getDocumentContextFromFile( + "./src/test/resources/references/ReferenceIndexCommonModuleLevel.bsl" + ); + + // Load the common module that will be referenced + var file = new File("src/test/resources/metadata/designer", + "CommonModules/ПервыйОбщийМодуль/Ext/Module.bsl"); + var uri = Absolute.uri(file); + var commonModuleContext = TestUtils.getDocumentContext( + uri, + FileUtils.readFileToString(file, StandardCharsets.UTF_8), + serverContext + ); + + referenceIndexFiller.fill(documentContext); + + // Модульная переменная МодульУровняМодуля используется в двух процедурах + var procMethod = commonModuleContext.getSymbolTree().getMethodSymbol("НеУстаревшаяПроцедура"); + assertThat(procMethod).isPresent(); + var referencesToProc = referenceIndex.getReferencesTo(procMethod.get()); + var referencesToProcFromTest = referencesToProc.stream() + .filter(ref -> ref.getUri().equals(documentContext.getUri())) + .toList(); + // Должно быть 2 вызова: из ПерваяПроцедура и из ТретьяПроцедура + assertThat(referencesToProcFromTest).hasSize(2); + + var funcMethod = commonModuleContext.getSymbolTree().getMethodSymbol("НеУстаревшаяФункция"); + assertThat(funcMethod).isPresent(); + var referencesToFunc = referenceIndex.getReferencesTo(funcMethod.get()); + var referencesToFuncFromTest = referencesToFunc.stream() + .filter(ref -> ref.getUri().equals(documentContext.getUri())) + .toList(); + // Должна быть 1 ссылка из ВтораяПроцедура (модульная переменная) + assertThat(referencesToFuncFromTest).hasSize(1); + } + + @Test + void testCommonModuleVariableIsolationBetweenMethods() throws IOException { + var path = Absolute.path("src/test/resources/metadata/designer"); + serverContext.setConfigurationRoot(path); + + var documentContext = TestUtils.getDocumentContextFromFile( + "./src/test/resources/references/ReferenceIndexCommonModuleIsolation.bsl" + ); + + // Load the common module that will be referenced + var file = new File("src/test/resources/metadata/designer", + "CommonModules/ПервыйОбщийМодуль/Ext/Module.bsl"); + var uri = Absolute.uri(file); + var commonModuleContext = TestUtils.getDocumentContext( + uri, + FileUtils.readFileToString(file, StandardCharsets.UTF_8), + serverContext + ); + + referenceIndexFiller.fill(documentContext); + + // В первой процедуре должна быть ссылка на НеУстаревшаяПроцедура + var procMethod = commonModuleContext.getSymbolTree().getMethodSymbol("НеУстаревшаяПроцедура"); + assertThat(procMethod).isPresent(); + var referencesToProc = referenceIndex.getReferencesTo(procMethod.get()); + var referencesToProcFromTest = referencesToProc.stream() + .filter(ref -> ref.getUri().equals(documentContext.getUri())) + .toList(); + assertThat(referencesToProcFromTest).hasSize(1); + + // Во второй процедуре НЕ должно быть ссылки на НеУстаревшаяФункция + // так как переменная Модуль там имеет другое значение (Неопределено) + var funcMethod = commonModuleContext.getSymbolTree().getMethodSymbol("НеУстаревшаяФункция"); + assertThat(funcMethod).isPresent(); + var referencesToFunc = referenceIndex.getReferencesTo(funcMethod.get()); + var referencesToFuncFromTest = referencesToFunc.stream() + .filter(ref -> ref.getUri().equals(documentContext.getUri())) + .toList(); + assertThat(referencesToFuncFromTest).hasSize(0); + } + @Test void testHandleServerContextDocumentRemovedEvent() { // given diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexTest.java index 217cec0fe37..2ce77da2692 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexTest.java @@ -257,7 +257,8 @@ void testCantGetReferenceToNonExportCommonModuleMethod() { var documentContext = TestUtils.getDocumentContextFromFile(PATH_TO_FILE); var uri = documentContext.getUri(); - var position = new Position(4, 10); + // Position on "Тест" method name (non-export method) + var position = new Position(4, 24); // when var reference = referenceIndex.getReference(uri, position); @@ -274,24 +275,35 @@ void testGetReferencesFromLocalMethodSymbol() { var commonModuleContext = serverContext.getDocument("CommonModule.ПервыйОбщийМодуль", ModuleType.CommonModule).orElseThrow(); var commonModuleMethodSymbol = commonModuleContext.getSymbolTree().getMethodSymbol("УстаревшаяПроцедура").orElseThrow(); + var commonModuleSymbol = commonModuleContext.getSymbolTree().getModule(); var managerModuleContext = serverContext.getDocument("InformationRegister.РегистрСведений1", ModuleType.ManagerModule).orElseThrow(); var managerModuleMethodSymbol = managerModuleContext.getSymbolTree().getMethodSymbol("УстаревшаяПроцедура").orElseThrow(); var uri = documentContext.getUri().toString(); var locationLocal = new Location(uri, Ranges.create(1, 4, 16)); + var locationCommonModuleName1 = new Location(uri, Ranges.create(2, 4, 21)); // ПервыйОбщийМодуль on line 3 var locationCommonModule = new Location(uri, Ranges.create(2, 22, 41)); var locationManagerModule = new Location(uri, Ranges.create(3, 38, 57)); + var locationCommonModuleName2 = new Location(uri, Ranges.create(4, 4, 21)); // ПервыйОбщийМодуль on line 5 // when var references = referenceIndex.getReferencesFrom(localMethodSymbol); // then + // 5 references from ReferenceIndex.bsl: + // - line 2: local method call ИмяПроцедуры() + // - line 3: module name ПервыйОбщийМодуль + // - line 3: method УстаревшаяПроцедура in common module + // - line 4: method УстаревшаяПроцедура in manager module + // - line 5: module name ПервыйОбщийМодуль assertThat(references) - .hasSize(3) + .hasSize(5) .contains(Reference.of(localMethodSymbol, localMethodSymbol, locationLocal)) + .contains(Reference.of(localMethodSymbol, commonModuleSymbol, locationCommonModuleName1)) .contains(Reference.of(localMethodSymbol, commonModuleMethodSymbol, locationCommonModule)) .contains(Reference.of(localMethodSymbol, managerModuleMethodSymbol, locationManagerModule)) + .contains(Reference.of(localMethodSymbol, commonModuleSymbol, locationCommonModuleName2)) ; } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ModuleReferenceTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ModuleReferenceTest.java new file mode 100644 index 00000000000..de2c57550fa --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/utils/ModuleReferenceTest.java @@ -0,0 +1,120 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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.utils; + +import com.github._1c_syntax.bsl.languageserver.configuration.references.ReferencesOptions; +import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterEachTestMethod; +import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import com.github._1c_syntax.bsl.parser.BSLParser; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@CleanupContextBeforeClassAndAfterEachTestMethod +class ModuleReferenceTest { + + private static final ModuleReference.ParsedAccessors DEFAULT_ACCESSORS = + ModuleReference.parseAccessors(new ReferencesOptions().getCommonModuleAccessors()); + + @Test + void testDetectCommonModuleExpression() { + var code = """ + Процедура Тест() + Модуль = ОбщегоНазначения.ОбщийМодуль("ПервыйОбщийМодуль"); + КонецПроцедуры"""; + + var documentContext = TestUtils.getDocumentContext(code); + var ast = documentContext.getAst(); + + // Find assignment + var assignments = new ArrayList(); + Trees.findAllRuleNodes(ast, BSLParser.RULE_assignment).forEach(node -> + assignments.add((BSLParser.AssignmentContext) node) + ); + + assertThat(assignments).hasSize(1); + + var expression = assignments.get(0).expression(); + assertThat(ModuleReference.isCommonModuleExpression(expression, DEFAULT_ACCESSORS)).isTrue(); + + var moduleName = ModuleReference.extractCommonModuleName(expression, DEFAULT_ACCESSORS); + assertThat(moduleName).isPresent(); + assertThat(moduleName.get()).isEqualTo("ПервыйОбщийМодуль"); + } + + @Test + void testCustomAccessor() { + var code = """ + Процедура Тест() + Модуль = МойМодуль.ПолучитьОбщийМодуль("ТестовыйМодуль"); + КонецПроцедуры"""; + + var documentContext = TestUtils.getDocumentContext(code); + var ast = documentContext.getAst(); + + var assignments = new ArrayList(); + Trees.findAllRuleNodes(ast, BSLParser.RULE_assignment).forEach(node -> + assignments.add((BSLParser.AssignmentContext) node) + ); + + assertThat(assignments).hasSize(1); + + var expression = assignments.get(0).expression(); + + // With default accessors - should not match + assertThat(ModuleReference.isCommonModuleExpression(expression, DEFAULT_ACCESSORS)).isFalse(); + + // With custom accessor - should match + var customAccessors = ModuleReference.parseAccessors(List.of("МойМодуль.ПолучитьОбщийМодуль")); + assertThat(ModuleReference.isCommonModuleExpression(expression, customAccessors)).isTrue(); + + var moduleName = ModuleReference.extractCommonModuleName(expression, customAccessors); + assertThat(moduleName).isPresent(); + assertThat(moduleName.get()).isEqualTo("ТестовыйМодуль"); + } + + @Test + void testParseAccessors() { + var accessors = List.of( + "ОбщийМодуль", + "CommonModule", + "ОбщегоНазначения.ОбщийМодуль", + "Common.CommonModule" + ); + + var parsed = ModuleReference.parseAccessors(accessors); + + // Проверяем локальные методы + assertThat(parsed.localMethods()).containsExactlyInAnyOrder("общиймодуль", "commonmodule"); + + // Проверяем пары модуль.метод + assertThat(parsed.moduleMethodPairs()).containsKey("общегоназначения"); + assertThat(parsed.moduleMethodPairs().get("общегоназначения")).contains("общиймодуль"); + assertThat(parsed.moduleMethodPairs()).containsKey("common"); + assertThat(parsed.moduleMethodPairs().get("common")).contains("commonmodule"); + } +} diff --git a/src/test/resources/providers/definitionCommonModule.bsl b/src/test/resources/providers/definitionCommonModule.bsl new file mode 100644 index 00000000000..c6d722801b5 --- /dev/null +++ b/src/test/resources/providers/definitionCommonModule.bsl @@ -0,0 +1,2 @@ +// Тест для definition общего модуля +ПервыйОбщийМодуль.НеУстаревшаяПроцедура(); diff --git a/src/test/resources/references/ReferenceIndexCommonModuleIsolation.bsl b/src/test/resources/references/ReferenceIndexCommonModuleIsolation.bsl new file mode 100644 index 00000000000..87e3b40262d --- /dev/null +++ b/src/test/resources/references/ReferenceIndexCommonModuleIsolation.bsl @@ -0,0 +1,22 @@ +// Тест для проверки изоляции mappings между методами + +Процедура ПерваяПроцедура() + + // В первой процедуре переменная ссылается на общий модуль + Модуль = ОбщегоНазначения.ОбщийМодуль("ПервыйОбщийМодуль"); + Модуль.НеУстаревшаяПроцедура(); // Должно индексироваться + +КонецПроцедуры + +Процедура ВтораяПроцедура() + + // Во второй процедуре переменная с тем же именем используется для чего-то другого + Модуль = Неопределено; + + // Этот вызов НЕ должен индексироваться как вызов общего модуля + Попытка + Модуль.НеУстаревшаяФункция(); + Исключение + КонецПопытки; + +КонецПроцедуры diff --git a/src/test/resources/references/ReferenceIndexCommonModuleLevel.bsl b/src/test/resources/references/ReferenceIndexCommonModuleLevel.bsl new file mode 100644 index 00000000000..a0c34391a44 --- /dev/null +++ b/src/test/resources/references/ReferenceIndexCommonModuleLevel.bsl @@ -0,0 +1,40 @@ +// Тест для проверки работы с модульными переменными + +Перем МодульУровняМодуля; // Модульная переменная + +Процедура ПерваяПроцедура() + + // Устанавливаем модульную переменную в первой процедуре + МодульУровняМодуля = ОбщегоНазначения.ОбщийМодуль("ПервыйОбщийМодуль"); + МодульУровняМодуля.НеУстаревшаяПроцедура(); // Должно индексироваться + +КонецПроцедуры + +Процедура ВтораяПроцедура() + + // Используем ту же модульную переменную во второй процедуре + // Mapping должен сохраниться из первой процедуры + МодульУровняМодуля.НеУстаревшаяФункция(); // Должно индексироваться + +КонецПроцедуры + +Процедура ТретьяПроцедура() + + // Локальная переменная с тем же именем что и в другой процедуре + Локальная = ОбщегоНазначения.ОбщийМодуль("ПервыйОбщийМодуль"); + Локальная.НеУстаревшаяПроцедура(); // Должно индексироваться + +КонецПроцедуры + +Процедура ЧетвертаяПроцедура() + + // Локальная переменная с тем же именем - не должна иметь mapping из третьей процедуры + Локальная = Неопределено; + + // Этот вызов НЕ должен индексироваться + Попытка + Локальная.НеУстаревшаяПроцедура(); + Исключение + КонецПопытки; + +КонецПроцедуры diff --git a/src/test/resources/references/ReferenceIndexCommonModuleReassignment.bsl b/src/test/resources/references/ReferenceIndexCommonModuleReassignment.bsl new file mode 100644 index 00000000000..5fe20160950 --- /dev/null +++ b/src/test/resources/references/ReferenceIndexCommonModuleReassignment.bsl @@ -0,0 +1,31 @@ +// Тест для проверки очистки mapping при переназначении переменной + +Процедура ТестПереназначения() + + // Сначала присваиваем ссылку на общий модуль + МодульДоступа = ОбщегоНазначения.ОбщийМодуль("ПервыйОбщийМодуль"); + МодульДоступа.НеУстаревшаяПроцедура(); // Должно быть найдено + + // Теперь переназначаем переменную на обычное значение + МодульДоступа = Неопределено; + + // После переназначения эти вызовы НЕ должны индексироваться как вызовы общего модуля + // (это будут вызовы на неопределённом значении, что является ошибкой, но это не наша проблема) + Попытка + МодульДоступа.НеУстаревшаяФункция(); // НЕ должно индексироваться как вызов общего модуля + Исключение + КонецПопытки; + +КонецПроцедуры + +Процедура ТестПереназначенияНаДругойМодуль() + + // Присваиваем один модуль + Модуль = ОбщегоНазначения.ОбщийМодуль("ПервыйОбщийМодуль"); + Модуль.НеУстаревшаяПроцедура(); // Должно индексироваться + + // Переназначаем на другой модуль + Модуль = ОбщегоНазначения.ОбщийМодуль("ГлобальныйСерверныйМодуль"); + // После переназначения старый mapping должен быть очищен + +КонецПроцедуры diff --git a/src/test/resources/references/ReferenceIndexCommonModuleVariable.bsl b/src/test/resources/references/ReferenceIndexCommonModuleVariable.bsl new file mode 100644 index 00000000000..81f2c546fd5 --- /dev/null +++ b/src/test/resources/references/ReferenceIndexCommonModuleVariable.bsl @@ -0,0 +1,18 @@ +// Тест для поддержки поиска ссылок на метод общего модуля, +// полученный через ОбщегоНазначения.ОбщийМодуль + +Процедура Тест() + + // Паттерн 1: Прямое обращение через ОбщегоНазначения.ОбщийМодуль + МодульУправлениеДоступом = ОбщегоНазначения.ОбщийМодуль("ПервыйОбщийМодуль"); + МодульУправлениеДоступом.НеУстаревшаяПроцедура(); + + // Паттерн 2: Вызов функции общего модуля через переменную + Результат = МодульУправлениеДоступом.НеУстаревшаяФункция(); + + // Паттерн 3: Вызов функции в условии + Если МодульУправлениеДоступом.НеУстаревшаяФункция() Тогда + // что-то делаем + КонецЕсли; + +КонецПроцедуры