diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/symbol/AnnotationSymbol.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/symbol/AnnotationSymbol.java new file mode 100644 index 00000000000..06319c9a443 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/symbol/AnnotationSymbol.java @@ -0,0 +1,85 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2024 + * 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.context.symbol; + +import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; +import com.github._1c_syntax.bsl.languageserver.context.symbol.description.MethodDescription; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Setter; +import lombok.ToString; +import lombok.Value; +import lombok.experimental.NonFinal; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.SymbolKind; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +@Value +@Builder +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString(exclude = {"parent"}) +public class AnnotationSymbol implements SourceDefinedSymbol, Describable { + + String name; + + SymbolKind symbolKind; + + @EqualsAndHashCode.Include + DocumentContext owner; + + Range range; + + @EqualsAndHashCode.Include + Range selectionRange; + + @Setter + @NonFinal + @Builder.Default + Optional parent = Optional.empty(); + + Optional description; + + @Override + public List getChildren() { + return Collections.emptyList(); + } + + @Override + public void accept(SymbolTreeVisitor visitor) { + // no-op + } + + public static AnnotationSymbol from(String name, MethodSymbol methodSymbol) { + return AnnotationSymbol.builder() + .name(name) + .symbolKind(SymbolKind.TypeParameter) + .owner(methodSymbol.getOwner()) + .range(methodSymbol.getRange()) + .selectionRange(methodSymbol.getSelectionRange()) + .description(methodSymbol.getDescription()) + .parent(Optional.of(methodSymbol)) + .build(); + } +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/AnnotationSymbolMarkupContentBuilder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/AnnotationSymbolMarkupContentBuilder.java new file mode 100644 index 00000000000..c9724ed4099 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/AnnotationSymbolMarkupContentBuilder.java @@ -0,0 +1,94 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2024 + * 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.AnnotationSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol; +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.StringJoiner; + +/** + * Построитель контента для всплывающего окна для {@link AnnotationSymbol}. + */ +@Component +@RequiredArgsConstructor +public class AnnotationSymbolMarkupContentBuilder implements MarkupContentBuilder { + + private final DescriptionFormatter descriptionFormatter; + + @Override + public MarkupContent getContent(AnnotationSymbol symbol) { + var maybeMethodSymbol = symbol.getParent(); + if (maybeMethodSymbol.filter(MethodSymbol.class::isInstance).isEmpty()) { + return new MarkupContent(MarkupKind.MARKDOWN, ""); + } + + var markupBuilder = new StringJoiner("\n"); + var methodSymbol = (MethodSymbol) maybeMethodSymbol.get(); + + // сигнатура + // местоположение метода + // описание метода + // параметры + // примеры + // варианты вызова + + // сигнатура + String signature = descriptionFormatter.getSignature(symbol, methodSymbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, signature); + + // местоположение метода + String methodLocation = descriptionFormatter.getLocation(methodSymbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, methodLocation); + + // описание метода + String purposeSection = descriptionFormatter.getPurposeSection(methodSymbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, purposeSection); + + // параметры + String parametersSection = descriptionFormatter.getParametersSection(methodSymbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, parametersSection); + + // примеры + String examplesSection = descriptionFormatter.getExamplesSection(methodSymbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, examplesSection); + + // варианты вызова + String callOptionsSection = descriptionFormatter.getCallOptionsSection(methodSymbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, callOptionsSection); + + String content = markupBuilder.toString(); + + return new MarkupContent(MarkupKind.MARKDOWN, content); + } + + @Override + public SymbolKind getSymbolKind() { + return SymbolKind.TypeParameter; + } + +} 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 new file mode 100644 index 00000000000..277aac1ef03 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter.java @@ -0,0 +1,356 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2024 + * 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.AnnotationSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol; +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; +import com.github._1c_syntax.bsl.languageserver.context.symbol.description.ParameterDescription; +import com.github._1c_syntax.bsl.languageserver.context.symbol.description.TypeDescription; +import com.github._1c_syntax.bsl.languageserver.utils.MdoRefBuilder; +import com.github._1c_syntax.bsl.languageserver.utils.Resources; +import lombok.RequiredArgsConstructor; +import org.eclipse.lsp4j.SymbolKind; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Component +@RequiredArgsConstructor +public class DescriptionFormatter { + + private static final String PROCEDURE_KEY = "procedure"; + private static final String FUNCTION_KEY = "function"; + private static final String ANNOTATION_KEY = "annotation"; + private static final String EXPORT_KEY = "export"; + private static final String VAL_KEY = "val"; + private static final String VARIABLE_KEY = "var"; + private static final String PARAMETERS_KEY = "parameters"; + private static final String RETURNED_VALUE_KEY = "returnedValue"; + private static final String EXAMPLES_KEY = "examples"; + private static final String CALL_OPTIONS_KEY = "callOptions"; + private static final String PARAMETER_TEMPLATE = "* **%s**: %s"; + + private final Resources resources; + + public void addSectionIfNotEmpty(StringJoiner markupBuilder, String newContent) { + if (!newContent.isEmpty()) { + markupBuilder.add(newContent); + markupBuilder.add(""); + markupBuilder.add("---"); + } + } + + public String getPurposeSection(MethodSymbol methodSymbol) { + return methodSymbol.getDescription() + .map(MethodDescription::getPurposeDescription) + .orElse(""); + } + + public String getParametersSection(MethodSymbol methodSymbol) { + var result = new StringJoiner(" \n"); // два пробела + methodSymbol.getParameters().forEach(parameterDefinition -> + result.add(parameterToString(parameterDefinition)) + ); + + var parameters = result.toString(); + + if (!parameters.isBlank()) { + var parametersSection = new StringJoiner("\n"); + String header = "**" + getResourceString(PARAMETERS_KEY) + ":**"; + parametersSection.add(header); + parametersSection.add(""); + parametersSection.add(parameters); + return parametersSection.toString(); + } + + return ""; + } + + public String getReturnedValueSection(MethodSymbol methodSymbol) { + var result = new StringJoiner(" \n"); // два пробела + methodSymbol.getDescription().ifPresent((MethodDescription methodDescription) -> { + Map typesMap = typesToMap(methodDescription.getReturnedValue(), 0); + result.add(typesMapToString(typesMap, 1)); + }); + + var returnedValue = result.toString(); + + if (!returnedValue.isEmpty()) { + returnedValue = "**" + getResourceString(RETURNED_VALUE_KEY) + ":**\n\n" + returnedValue; + } + + return returnedValue; + } + + public String getExamplesSection(MethodSymbol methodSymbol) { + var examples = methodSymbol.getDescription() + .map(MethodDescription::getExamples) + .orElseGet(Collections::emptyList); + return getSectionWithCodeFences(examples, EXAMPLES_KEY); + } + + public String getCallOptionsSection(MethodSymbol methodSymbol) { + var callOptions = methodSymbol.getDescription() + .map(MethodDescription::getCallOptions) + .orElseGet(Collections::emptyList); + return getSectionWithCodeFences(callOptions, CALL_OPTIONS_KEY); + } + + public String getSectionWithCodeFences(Collection codeBlocks, String resourceKey) { + String codeFences = codeBlocks + .stream() + .map(codeBlock -> "```bsl\n" + codeBlock + "\n```") + .collect(Collectors.joining("\n")); + + if (!codeFences.isEmpty()) { + codeFences = "**" + getResourceString(resourceKey) + ":**\n\n" + codeFences; + } + + return codeFences; + } + + public String getLocation(MethodSymbol symbol) { + var documentContext = symbol.getOwner(); + var startPosition = symbol.getSelectionRange().getStart(); + String mdoRef = MdoRefBuilder.getMdoRef(documentContext); + + return String.format( + "[%s](%s#%d)", + mdoRef, + documentContext.getUri(), + startPosition.getLine() + 1 + ); + } + + public String getLocation(VariableSymbol symbol) { + var documentContext = symbol.getOwner(); + var startPosition = symbol.getSelectionRange().getStart(); + String mdoRef = MdoRefBuilder.getMdoRef(documentContext); + + String parentPostfix = symbol.getRootParent(SymbolKind.Method) + .map(sourceDefinedSymbol -> "." + sourceDefinedSymbol.getName()) + .orElse(""); + mdoRef += parentPostfix; + + return String.format( + "[%s](%s#%d)", + mdoRef, + documentContext.getUri(), + startPosition.getLine() + 1 + ); + } + + public String getSignature(MethodSymbol methodSymbol) { + var signatureTemplate = "```bsl\n%s %s(%s)%s%s\n```"; + + String methodKind; + if (methodSymbol.isFunction()) { + methodKind = getResourceString(FUNCTION_KEY); + } else { + methodKind = getResourceString(PROCEDURE_KEY); + } + String methodName = methodSymbol.getName(); + + var parameters = getParametersDescriptionPart(methodSymbol); + var returnedValueType = getReturnedValueTypeDescriptionPart(methodSymbol); + String export = methodSymbol.isExport() ? (" " + getResourceString(EXPORT_KEY)) : ""; + + return String.format( + signatureTemplate, + methodKind, + methodName, + parameters, + export, + returnedValueType + ); + } + + public String getSignature(AnnotationSymbol symbol, MethodSymbol methodSymbol) { + var signatureTemplate = "```bsl\n%s &%s(%s)\n```"; + + var annotationKind = getResourceString(ANNOTATION_KEY); + var annotationName = symbol.getName(); + + var parameters = getParametersDescriptionPart(methodSymbol); + + return String.format( + signatureTemplate, + annotationKind, + annotationName, + parameters + ); + } + + public String getSignature(VariableSymbol symbol) { + var signatureTemplate = "```bsl\n%s %s%s\n```"; + + var varKey = getResourceString(VARIABLE_KEY); + var name = symbol.getName(); + var export = symbol.isExport() ? (" " + getResourceString(EXPORT_KEY)) : ""; + + return String.format( + signatureTemplate, + varKey, + name, + export + ); + } + + private String getParametersDescriptionPart(MethodSymbol methodSymbol) { + var parametersDescription = new StringJoiner(", "); + methodSymbol.getParameters().forEach((ParameterDefinition parameterDefinition) -> { + var parameter = ""; + var parameterName = parameterDefinition.getName(); + + if (parameterDefinition.isByValue()) { + parameter = parameter + getResourceString(VAL_KEY) + " "; + } + parameter += parameterName; + + var parameterTypes = parameterDefinition.getDescription() + .map(ParameterDescription::getTypes) + .map(DescriptionFormatter::getTypes) + .orElse(""); + + if (!parameterTypes.isEmpty()) { + parameter += ": " + parameterTypes; + } + + if (parameterDefinition.isOptional()) { + parameter += " = "; + parameter += parameterDefinition.getDefaultValue().getValue(); + } + + parametersDescription.add(parameter); + }); + + return parametersDescription.toString(); + } + + private static String getReturnedValueTypeDescriptionPart(MethodSymbol methodSymbol) { + String returnedValueType = methodSymbol.getDescription() + .map(MethodDescription::getReturnedValue) + .map(DescriptionFormatter::getTypes) + .orElse(""); + if (!returnedValueType.isEmpty()) { + returnedValueType = ": " + returnedValueType; + } + return returnedValueType; + } + + private static String getTypes(List typeDescriptions) { + return typeDescriptions.stream() + .map(TypeDescription::getName) + .flatMap(parameterType -> Stream.of(parameterType.split(","))) + .map(String::trim) + .collect(Collectors.joining(" | ")); + } + + + public String parameterToString(ParameterDescription parameter, int level) { + var result = new StringJoiner(" \n"); // два пробела + Map typesMap = typesToMap(parameter.getTypes(), level); + var parameterTemplate = " ".repeat(level) + PARAMETER_TEMPLATE; + + if (typesMap.size() == 1) { + result.add(String.format(parameterTemplate, + parameter.getName(), + typesMapToString(typesMap, 0))); + } else { + result.add(String.format(parameterTemplate, parameter.getName(), "")); + result.add(typesMapToString(typesMap, level + 1)); + } + return result.toString(); + } + + public String parameterToString(ParameterDefinition parameterDefinition) { + var level = 0; + var parameterDescription = parameterDefinition.getDescription(); + if (parameterDescription.isPresent()) { + return parameterToString(parameterDescription.get(), level); + } + + return String.format(PARAMETER_TEMPLATE, parameterDefinition.getName(), ""); + } + + private Map typesToMap(List parameterTypes, int level) { + Map types = new HashMap<>(); + + parameterTypes.forEach((TypeDescription type) -> { + var typeDescription = typeToString(type, level); + String typeName; + if (type.isHyperlink()) { + typeName = String.format("[%s](%s)", type.getName(), type.getLink()); + } else { + typeName = String.format("`%s`", type.getName()); + } + + types.merge(typeDescription, typeName, (oldValue, newValue) -> String.format("%s | %s", oldValue, newValue)); + }); + return types; + } + + private static String typesMapToString(Map types, int level) { + var result = new StringJoiner(" \n"); // два пробела + var indent = "  ".repeat(level); + types.forEach((String key, String value) -> { + if (key.isBlank()) { + result.add(value); + } else { + result.add(String.format("%s%s %s", indent, value, key)); + } + }); + return result.toString(); + } + + private String typeToString(TypeDescription type, int level) { + var result = new StringJoiner(" \n"); // два пробела + var description = type.getDescription().replace("\n", "
" + "  ".repeat(level + 1)); + + if (!description.isBlank()) { + description = "- " + description; + } + if (!type.getParameters().isEmpty()) { + description += ":"; + } + + result.add(description); + type.getParameters().forEach((ParameterDescription parameter) -> + result.add(parameterToString(parameter, level + 1))); + return result.toString(); + } + + private String getResourceString(String key) { + return resources.getResourceString(getClass(), key); + } + +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder.java index de16d79d37f..ed7360d9342 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder.java @@ -21,27 +21,14 @@ */ package com.github._1c_syntax.bsl.languageserver.hover; -import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration; import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol; -import com.github._1c_syntax.bsl.languageserver.context.symbol.ParameterDefinition; -import com.github._1c_syntax.bsl.languageserver.context.symbol.description.MethodDescription; -import com.github._1c_syntax.bsl.languageserver.context.symbol.description.ParameterDescription; -import com.github._1c_syntax.bsl.languageserver.context.symbol.description.TypeDescription; -import com.github._1c_syntax.bsl.languageserver.utils.MdoRefBuilder; -import com.github._1c_syntax.bsl.languageserver.utils.Resources; 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.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.StringJoiner; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Построитель контента для всплывающего окна для {@link MethodSymbol}. @@ -60,7 +47,7 @@ public class MethodSymbolMarkupContentBuilder implements MarkupContentBuilder - result.add(parameterToString(parameterDefinition)) - ); - - var parameters = result.toString(); - - if (!parameters.isBlank()) { - var parametersSection = new StringJoiner("\n"); - String header = "**" + getResourceString(PARAMETERS_KEY) + ":**"; - parametersSection.add(header); - parametersSection.add(""); - parametersSection.add(parameters); - return parametersSection.toString(); - } - - return ""; - } - - private String getReturnedValueSection(MethodSymbol methodSymbol) { - var result = new StringJoiner(" \n"); // два пробела - methodSymbol.getDescription().ifPresent((MethodDescription methodDescription) -> { - Map typesMap = typesToMap(methodDescription.getReturnedValue(), 0); - result.add(typesMapToString(typesMap, 1)); - }); - - var returnedValue = result.toString(); - - if (!returnedValue.isEmpty()) { - returnedValue = "**" + getResourceString(RETURNED_VALUE_KEY) + ":**\n\n" + returnedValue; - } - - return returnedValue; - } - - private String getExamplesSection(MethodSymbol methodSymbol) { - var examples = methodSymbol.getDescription() - .map(MethodDescription::getExamples) - .orElseGet(Collections::emptyList); - return getSectionWithCodeFences(examples, EXAMPLES_KEY); - } - - private String getCallOptionsSection(MethodSymbol methodSymbol) { - var callOptions = methodSymbol.getDescription() - .map(MethodDescription::getCallOptions) - .orElseGet(Collections::emptyList); - return getSectionWithCodeFences(callOptions, CALL_OPTIONS_KEY); - } - - private String getSectionWithCodeFences(List codeBlocks, String resourceKey) { - String codeFences = codeBlocks - .stream() - .map(codeBlock -> "```bsl\n" + codeBlock + "\n```") - .collect(Collectors.joining("\n")); - - if (!codeFences.isEmpty()) { - codeFences = "**" + getResourceString(resourceKey) + ":**\n\n" + codeFences; - } - - return codeFences; - } - - private static String getLocation(MethodSymbol symbol) { - var documentContext = symbol.getOwner(); - var startPosition = symbol.getSelectionRange().getStart(); - String mdoRef = MdoRefBuilder.getMdoRef(documentContext); - - return String.format( - "[%s](%s#%d)", - mdoRef, - documentContext.getUri(), - startPosition.getLine() + 1 - ); - } - - private String getSignature(MethodSymbol methodSymbol) { - String signatureTemplate = "```bsl\n%s %s(%s)%s%s\n```"; - - String methodKind; - if (methodSymbol.isFunction()) { - methodKind = getResourceString(FUNCTION_KEY); - } else { - methodKind = getResourceString(PROCEDURE_KEY); - } - String methodName = methodSymbol.getName(); - - var parametersDescription = new StringJoiner(", "); - methodSymbol.getParameters().forEach((ParameterDefinition parameterDefinition) -> { - var parameter = ""; - var parameterName = parameterDefinition.getName(); - - if (parameterDefinition.isByValue()) { - parameter = parameter + getResourceString(VAL_KEY) + " "; - } - parameter += parameterName; - - var parameterTypes = parameterDefinition.getDescription() - .map(ParameterDescription::getTypes) - .map(MethodSymbolMarkupContentBuilder::getTypes) - .orElse(""); - - if (!parameterTypes.isEmpty()) { - parameter += ": " + parameterTypes; - } - - if (parameterDefinition.isOptional()) { - parameter += " = "; - parameter += parameterDefinition.getDefaultValue().getValue(); - } - - parametersDescription.add(parameter); - }); - var parameters = parametersDescription.toString(); - - String returnedValueType = methodSymbol.getDescription() - .map(MethodDescription::getReturnedValue) - .map(MethodSymbolMarkupContentBuilder::getTypes) - .orElse(""); - if (!returnedValueType.isEmpty()) { - returnedValueType = ": " + returnedValueType; - } - - String export = methodSymbol.isExport() ? (" " + getResourceString(EXPORT_KEY)) : ""; - - return String.format( - signatureTemplate, - methodKind, - methodName, - parameters, - export, - returnedValueType - ); - } - - private static String getTypes(List typeDescriptions) { - return typeDescriptions.stream() - .map(TypeDescription::getName) - .flatMap(parameterType -> Stream.of(parameterType.split(","))) - .map(String::trim) - .collect(Collectors.joining(" | ")); - } - - private String getResourceString(String key) { - return Resources.getResourceString(configuration.getLanguage(), getClass(), key); - } - - public static String parameterToString(ParameterDescription parameter, int level) { - var result = new StringJoiner(" \n"); // два пробела - Map typesMap = typesToMap(parameter.getTypes(), level); - var parameterTemplate = " ".repeat(level) + PARAMETER_TEMPLATE; - - if (typesMap.size() == 1) { - result.add(String.format(parameterTemplate, - parameter.getName(), - typesMapToString(typesMap, 0))); - } else { - result.add(String.format(parameterTemplate, parameter.getName(), "")); - result.add(typesMapToString(typesMap, level + 1)); - } - return result.toString(); - } - - public static String parameterToString(ParameterDefinition parameterDefinition) { - int level = 0; - if (parameterDefinition.getDescription().isPresent()) { - return parameterToString(parameterDefinition.getDescription().get(), level); - } - - return String.format(PARAMETER_TEMPLATE, parameterDefinition.getName(), ""); - } - - private static Map typesToMap(List parameterTypes, int level) { - Map types = new HashMap<>(); - - parameterTypes.forEach((TypeDescription type) -> { - var typeDescription = typeToString(type, level); - String typeName; - if (type.isHyperlink()) { - typeName = String.format("[%s](%s)", type.getName(), type.getLink()); - } else { - typeName = String.format("`%s`", type.getName()); - } - - types.merge(typeDescription, typeName, (oldValue, newValue) -> String.format("%s | %s", oldValue, newValue)); - }); - return types; - } - - private static String typesMapToString(Map types, int level) { - var result = new StringJoiner(" \n"); // два пробела - var indent = "  ".repeat(level); - types.forEach((String key, String value) -> { - if (key.isBlank()) { - result.add(value); - } else { - result.add(String.format("%s%s %s", indent, value, key)); - } - }); - return result.toString(); - } - - private static String typeToString(TypeDescription type, int level) { - var result = new StringJoiner(" \n"); // два пробела - var description = type.getDescription().replace("\n", "
" + "  ".repeat(level + 1)); - - if (!description.isBlank()) { - description = "- " + description; - } - if (!type.getParameters().isEmpty()) { - description += ":"; - } - - result.add(description); - type.getParameters().forEach((ParameterDescription parameter) -> - result.add(parameterToString(parameter, level + 1))); - return result.toString(); - } } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder.java index 0b442a85e0f..7ca24d7b48e 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder.java @@ -24,8 +24,6 @@ import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration; import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol; import com.github._1c_syntax.bsl.languageserver.context.symbol.variable.VariableDescription; -import com.github._1c_syntax.bsl.languageserver.utils.MdoRefBuilder; -import com.github._1c_syntax.bsl.languageserver.utils.Resources; import lombok.RequiredArgsConstructor; import org.eclipse.lsp4j.MarkupContent; import org.eclipse.lsp4j.MarkupKind; @@ -42,6 +40,7 @@ public class VariableSymbolMarkupContentBuilder implements MarkupContentBuilder< private static final String EXPORT_KEY = "export"; private final LanguageServerConfiguration configuration; + private final DescriptionFormatter descriptionFormatter; @Override public MarkupContent getContent(VariableSymbol symbol) { @@ -52,22 +51,22 @@ public MarkupContent getContent(VariableSymbol symbol) { // описание переменной // сигнатура - String signature = getSignature(symbol); - addSectionIfNotEmpty(markupBuilder, signature); + String signature = descriptionFormatter.getSignature(symbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, signature); // местоположение переменной - String location = getLocation(symbol); - addSectionIfNotEmpty(markupBuilder, location); + String location = descriptionFormatter.getLocation(symbol); + descriptionFormatter.addSectionIfNotEmpty(markupBuilder, location); // описание переменной symbol.getDescription() .map(VariableDescription::getPurposeDescription) - .ifPresent(description -> addSectionIfNotEmpty(markupBuilder, description)); + .ifPresent(description -> descriptionFormatter.addSectionIfNotEmpty(markupBuilder, description)); symbol.getDescription() .flatMap(VariableDescription::getTrailingDescription) .map(VariableDescription::getPurposeDescription) - .ifPresent(trailingDescription -> addSectionIfNotEmpty(markupBuilder, trailingDescription)); + .ifPresent(trailingDescription -> descriptionFormatter.addSectionIfNotEmpty(markupBuilder, trailingDescription)); String content = markupBuilder.toString(); @@ -79,48 +78,4 @@ public SymbolKind getSymbolKind() { return SymbolKind.Variable; } - private String getSignature(VariableSymbol symbol) { - String signatureTemplate = "```bsl\n%s %s%s\n```"; - - String varKey = getResourceString(VARIABLE_KEY); - String name = symbol.getName(); - String export = symbol.isExport() ? (" " + getResourceString(EXPORT_KEY)) : ""; - - return String.format( - signatureTemplate, - varKey, - name, - export - ); - } - - private static String getLocation(VariableSymbol symbol) { - var documentContext = symbol.getOwner(); - var startPosition = symbol.getSelectionRange().getStart(); - String mdoRef = MdoRefBuilder.getMdoRef(symbol.getOwner()); - - String parentPostfix = symbol.getRootParent(SymbolKind.Method) - .map(sourceDefinedSymbol -> "." + sourceDefinedSymbol.getName()) - .orElse(""); - mdoRef += parentPostfix; - - return String.format( - "[%s](%s#%d)", - mdoRef, - documentContext.getUri(), - startPosition.getLine() + 1 - ); - } - - private static void addSectionIfNotEmpty(StringJoiner markupBuilder, String newContent) { - if (!newContent.isEmpty()) { - markupBuilder.add(newContent); - markupBuilder.add(""); - markupBuilder.add("---"); - } - } - - private String getResourceString(String key) { - return Resources.getResourceString(configuration.getLanguage(), getClass(), key); - } } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/SourceDefinedMethodCallInlayHintSupplier.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/SourceDefinedMethodCallInlayHintSupplier.java index d47e73023be..4ee1a9c05f2 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/SourceDefinedMethodCallInlayHintSupplier.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/SourceDefinedMethodCallInlayHintSupplier.java @@ -25,7 +25,7 @@ import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol; import com.github._1c_syntax.bsl.languageserver.context.symbol.ParameterDefinition; -import com.github._1c_syntax.bsl.languageserver.hover.MethodSymbolMarkupContentBuilder; +import com.github._1c_syntax.bsl.languageserver.hover.DescriptionFormatter; import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex; import com.github._1c_syntax.bsl.languageserver.references.model.Reference; import com.github._1c_syntax.bsl.languageserver.utils.Ranges; @@ -47,7 +47,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; /** * Поставщик подсказок о параметрах вызываемого метода. @@ -63,6 +62,7 @@ public class SourceDefinedMethodCallInlayHintSupplier implements InlayHintSuppli private final ReferenceIndex referenceIndex; private final LanguageServerConfiguration configuration; + private final DescriptionFormatter descriptionFormatter; @Override @@ -81,7 +81,7 @@ public List getInlayHints(DocumentContext documentContext, InlayHintP .filter(Reference::isSourceDefinedSymbolReference) .map(this::toInlayHints) .flatMap(Collection::stream) - .collect(Collectors.toList()); + .toList(); } @@ -129,7 +129,7 @@ private List toInlayHints(Reference reference) { return hints; }) .flatMap(Collection::stream) - .collect(Collectors.toList()); + .toList(); } @@ -164,9 +164,8 @@ private static void setPosition(InlayHint inlayHint, BSLParser.CallParamContext inlayHint.setPosition(position); } - private static void setTooltip(InlayHint inlayHint, ParameterDefinition parameter) { - // todo: refactor - var markdown = MethodSymbolMarkupContentBuilder.parameterToString(parameter); + private void setTooltip(InlayHint inlayHint, ParameterDefinition parameter) { + var markdown = descriptionFormatter.parameterToString(parameter); var tooltip = new MarkupContent(MarkupKind.MARKDOWN, markdown); inlayHint.setTooltip(tooltip); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/AnnotationReferenceFinder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/AnnotationReferenceFinder.java new file mode 100644 index 00000000000..96988a0e413 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/AnnotationReferenceFinder.java @@ -0,0 +1,128 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2024 + * 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.FileType; +import com.github._1c_syntax.bsl.languageserver.context.ServerContext; +import com.github._1c_syntax.bsl.languageserver.context.events.DocumentContextContentChangedEvent; +import com.github._1c_syntax.bsl.languageserver.context.events.ServerContextPopulatedEvent; +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.annotations.Annotation; +import com.github._1c_syntax.bsl.languageserver.references.model.Reference; +import com.github._1c_syntax.bsl.languageserver.utils.Methods; +import com.github._1c_syntax.bsl.languageserver.utils.Ranges; +import com.github._1c_syntax.bsl.languageserver.utils.Trees; +import com.github._1c_syntax.bsl.parser.BSLParser; +import lombok.RequiredArgsConstructor; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@RequiredArgsConstructor +public class AnnotationReferenceFinder implements ReferenceFinder { + + private final ServerContext serverContext; + private final Map registeredAnnotations = new ConcurrentHashMap<>(); + + @EventListener + public void handleContextRefresh(ServerContextPopulatedEvent event) { + registeredAnnotations.clear(); + serverContext.getDocuments() + .values() + .forEach(this::findAndRegisterAnnotation); + } + + @EventListener + public void handleDocumentContextChange(DocumentContextContentChangedEvent event) { + DocumentContext documentContext = event.getSource(); + var uri = documentContext.getUri(); + + registeredAnnotations.values() + .removeIf(annotationSymbol -> annotationSymbol.getOwner().getUri().equals(uri)); + + findAndRegisterAnnotation(documentContext); + } + + private void findAndRegisterAnnotation(DocumentContext documentContext) { + // In normal case this method may be called twice per each document context: + // 1. When the document context is created during the server context population or document opening + // 2. When server context is fully populated. + // This can lead to the situation when annotations registered from opened documents are cleared after populateContext step. + // Due to limitation of mechanism to only OS files, we can leave it as is for now, but it should be refactored in the future. + if (documentContext.getFileType() != FileType.OS) { + return; + } + + var symbolTree = documentContext.getSymbolTree(); + + Methods.getOscriptClassConstructor(symbolTree) + .flatMap(AnnotationReferenceFinder::findAnnotation) + .map(methodSymbolAnnotationPair -> AnnotationSymbol.from(getAnnotationName(methodSymbolAnnotationPair.getRight()), methodSymbolAnnotationPair.getLeft())) + .ifPresent(annotationSymbol -> registeredAnnotations.put(annotationSymbol.getName(), annotationSymbol)); + } + + @Override + public Optional findReference(URI uri, Position position) { + DocumentContext document = serverContext.getDocument(uri); + if (document == null || document.getFileType() != FileType.OS) { + return Optional.empty(); + } + + return Trees.findTerminalNodeContainsPosition(document.getAst(), position) + .filter(node -> node.getParent().getRuleContext().getRuleIndex() == BSLParser.RULE_annotationName) + .flatMap((TerminalNode annotationNode) -> { + var annotationName = annotationNode.getText(); + var annotationSymbol = registeredAnnotations.get(annotationName); + if (annotationSymbol == null) { + return Optional.empty(); + } + return Optional.of(Reference.of( + document.getSymbolTree().getModule(), + annotationSymbol, + new Location(uri.toString(), Ranges.create(annotationNode.getParent().getParent())) + )); + }); + } + + private static Optional> findAnnotation(MethodSymbol methodSymbol) { + return methodSymbol.getAnnotations().stream() + .filter(annotation -> annotation.getName().equalsIgnoreCase("Аннотация")) + .findFirst() + .filter(annotation -> annotation.getParameters().size() == 1) + .map(annotation -> Pair.of(methodSymbol, annotation)); + } + + private static String getAnnotationName(Annotation annotation) { + return annotation.getParameters().get(0).getValue(); + } +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Methods.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Methods.java index f223a5aa390..3a55bf8e80c 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Methods.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Methods.java @@ -21,6 +21,8 @@ */ package com.github._1c_syntax.bsl.languageserver.utils; +import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.SymbolTree; import com.github._1c_syntax.bsl.parser.BSLParser; import lombok.experimental.UtilityClass; import org.antlr.v4.runtime.Token; @@ -82,4 +84,9 @@ public static Optional getMethodName(BSLParser.LValueContext lValueContex .flatMap(Methods::getMethodName); } + public static Optional getOscriptClassConstructor(SymbolTree symbolTree) { + return symbolTree.getMethodSymbol("ПриСозданииОбъекта") + .or(() -> symbolTree.getMethodSymbol("OnObjectCreate")); + } + } diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder_en.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter_en.properties similarity index 61% rename from src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder_en.properties rename to src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter_en.properties index dd87e8b007d..10ca8a49e46 100644 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder_en.properties +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter_en.properties @@ -1,8 +1,10 @@ -callOptions=Сall options +annotation=Annotation +callOptions=Call options examples=Example export=Export function=Function parameters=Parameters procedure=Procedure returnedValue=Returns -val=Val \ No newline at end of file +val=Val +var=Var \ No newline at end of file diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder_ru.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter_ru.properties similarity index 77% rename from src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder_ru.properties rename to src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter_ru.properties index da11d16cb00..648351fc42b 100644 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilder_ru.properties +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/DescriptionFormatter_ru.properties @@ -1,3 +1,4 @@ +annotation=Аннотация callOptions=Варианты вызова examples=Пример export=Экспорт @@ -5,4 +6,5 @@ function=Функция parameters=Параметры procedure=Процедура returnedValue=Возвращаемое значение -val=Знач \ No newline at end of file +val=Знач +var=Перем \ No newline at end of file diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder_en.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder_en.properties deleted file mode 100644 index f352ad82aa2..00000000000 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder_en.properties +++ /dev/null @@ -1,2 +0,0 @@ -var=Var -export=Export diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder_ru.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder_ru.properties deleted file mode 100644 index 4ef82d6d56b..00000000000 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilder_ru.properties +++ /dev/null @@ -1,2 +0,0 @@ -var=Перем -export=Экспорт diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/context/AbstractServerContextAwareTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/context/AbstractServerContextAwareTest.java new file mode 100644 index 00000000000..0923a7e3dd7 --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/context/AbstractServerContextAwareTest.java @@ -0,0 +1,50 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2024 + * 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.context; + +import com.github._1c_syntax.utils.Absolute; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.PostConstruct; +import java.nio.file.Path; + +@SpringBootTest +public abstract class AbstractServerContextAwareTest { + @Autowired + protected ServerContext context; + + @PostConstruct + public void abstractServerContextAwareTestInit() { + context.clear(); + } + + protected void initServerContext(String path) { + var configurationRoot = Absolute.path(path); + initServerContext(configurationRoot); + } + + protected void initServerContext(Path configurationRoot) { + context.setConfigurationRoot(configurationRoot); + context.populateContext(); + } +} diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AbstractDiagnosticTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AbstractDiagnosticTest.java index 8d845642a63..a760546ff57 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AbstractDiagnosticTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AbstractDiagnosticTest.java @@ -22,11 +22,10 @@ package com.github._1c_syntax.bsl.languageserver.diagnostics; import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration; +import com.github._1c_syntax.bsl.languageserver.context.AbstractServerContextAwareTest; import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; -import com.github._1c_syntax.bsl.languageserver.context.ServerContext; import com.github._1c_syntax.bsl.languageserver.diagnostics.infrastructure.DiagnosticObjectProvider; import com.github._1c_syntax.bsl.languageserver.util.TestUtils; -import com.github._1c_syntax.utils.Absolute; import jakarta.annotation.PostConstruct; import lombok.SneakyThrows; import org.apache.commons.io.IOUtils; @@ -41,18 +40,15 @@ import org.springframework.boot.test.context.SpringBootTest; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; import java.util.Collections; import java.util.List; @SpringBootTest -abstract class AbstractDiagnosticTest { +abstract class AbstractDiagnosticTest extends AbstractServerContextAwareTest { @Autowired private DiagnosticObjectProvider diagnosticObjectProvider; @Autowired - protected ServerContext context; - @Autowired protected LanguageServerConfiguration configuration; private final Class diagnosticClass; @@ -65,20 +61,9 @@ abstract class AbstractDiagnosticTest { @PostConstruct public void init() { diagnosticInstance = diagnosticObjectProvider.get(diagnosticClass); - context.clear(); configuration.reset(); } - protected void initServerContext(String path) { - var configurationRoot = Absolute.path(path); - initServerContext(configurationRoot); - } - - protected void initServerContext(Path configurationRoot) { - context.setConfigurationRoot(configurationRoot); - context.populateContext(); - } - protected List getDiagnostics(DocumentContext documentContext) { return diagnosticInstance.getDiagnostics(documentContext); } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/AnnotationReferenceFinderTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/AnnotationReferenceFinderTest.java new file mode 100644 index 00000000000..0f639341f1e --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/references/AnnotationReferenceFinderTest.java @@ -0,0 +1,64 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2024 + * 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.AbstractServerContextAwareTest; +import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterClass; +import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import com.github._1c_syntax.bsl.languageserver.utils.Ranges; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.SymbolKind; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@CleanupContextBeforeClassAndAfterClass +class AnnotationReferenceFinderTest extends AbstractServerContextAwareTest { + + @Autowired + private AnnotationReferenceFinder referenceFinder; + + @Test + void findReference() { + // given + initServerContext("./src/test/resources/references/annotations"); + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/references/AnnotationReferenceFinder.os"); + + var module = documentContext.getSymbolTree().getModule(); + + // when + var optionalReference = referenceFinder.findReference(documentContext.getUri(), new Position(0, 2)); + + // then + assertThat(optionalReference) + .isPresent() + .hasValueSatisfying(reference -> assertThat(reference.getFrom()).isEqualTo(module)) + .hasValueSatisfying(reference -> assertThat(reference.getSymbol().getName()).isEqualTo("ТестоваяАннотация")) + .hasValueSatisfying(reference -> assertThat(reference.getSymbol().getSymbolKind()).isEqualTo(SymbolKind.TypeParameter)) + .hasValueSatisfying(reference -> assertThat(reference.getSelectionRange()).isEqualTo(Ranges.create(0, 0, 18))) + .hasValueSatisfying(reference -> assertThat(reference.getSourceDefinedSymbol().orElseThrow().getSelectionRange()).isEqualTo(Ranges.create(1, 10, 28))) + ; + } +} \ No newline at end of file diff --git a/src/test/resources/references/AnnotationReferenceFinder.os b/src/test/resources/references/AnnotationReferenceFinder.os new file mode 100644 index 00000000000..a6bb131844e --- /dev/null +++ b/src/test/resources/references/AnnotationReferenceFinder.os @@ -0,0 +1,8 @@ +&ТестоваяАннотация +Перем ТестоваяПеременная; + +&ТестоваяАннотация2 +Перем ТестоваяПеременная2; + +Процедура ПриСозданииОбъекта() +КонецПроцедуры \ No newline at end of file diff --git "a/src/test/resources/references/annotations/\320\242\320\265\321\201\321\202\320\276\320\262\320\260\321\217\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\217.os" "b/src/test/resources/references/annotations/\320\242\320\265\321\201\321\202\320\276\320\262\320\260\321\217\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\217.os" new file mode 100644 index 00000000000..e3b4f507ffa --- /dev/null +++ "b/src/test/resources/references/annotations/\320\242\320\265\321\201\321\202\320\276\320\262\320\260\321\217\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\217.os" @@ -0,0 +1,3 @@ +&Аннотация("ТестоваяАннотация") +Процедура ПриСозданииОбъекта() +КонецПроцедуры \ No newline at end of file diff --git "a/src/test/resources/references/annotations/\320\242\320\265\321\201\321\202\320\276\320\262\320\260\321\217\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\2172.os" "b/src/test/resources/references/annotations/\320\242\320\265\321\201\321\202\320\276\320\262\320\260\321\217\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\2172.os" new file mode 100644 index 00000000000..1bbb37f66e2 --- /dev/null +++ "b/src/test/resources/references/annotations/\320\242\320\265\321\201\321\202\320\276\320\262\320\260\321\217\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\2172.os" @@ -0,0 +1,3 @@ +&Аннотация("ТестоваяАннотация2") +Процедура ПриСозданииОбъекта() +КонецПроцедуры \ No newline at end of file