From e6c0d0349aa815a0cfd51ef74793523a3c7a534a Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Wed, 24 Apr 2024 18:00:34 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=D0=94=D1=80=D0=B0=D1=84=D1=82=20=D0=B2?= =?UTF-8?q?=D0=BD=D0=B5=D1=88=D0=BD=D0=B5=D0=B3=D0=BE=20=D0=B2=D1=8B=D1=87?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B8=D1=82=D0=B5=D0=BB=D1=8F=20=D1=82=D0=B8?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VariableSymbolMarkupContentBuilder.java | 21 ++ .../bsl/languageserver/types/Type.java | 29 +++ .../languageserver/types/TypeResolver.java | 181 ++++++++++++++++++ .../bsl/languageserver/utils/Trees.java | 5 +- .../types/TypeResolverTest.java | 114 +++++++++++ src/test/resources/types/TypeResolver.os | 16 ++ 6 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/types/Type.java create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java create mode 100644 src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java create mode 100644 src/test/resources/types/TypeResolver.os 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..0d29149c0d2 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,6 +24,8 @@ 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.types.Type; +import com.github._1c_syntax.bsl.languageserver.types.TypeResolver; import com.github._1c_syntax.bsl.languageserver.utils.MdoRefBuilder; import com.github._1c_syntax.bsl.languageserver.utils.Resources; import lombok.RequiredArgsConstructor; @@ -32,7 +34,9 @@ import org.eclipse.lsp4j.SymbolKind; import org.springframework.stereotype.Component; +import java.util.List; import java.util.StringJoiner; +import java.util.stream.Collectors; @Component @RequiredArgsConstructor @@ -41,6 +45,7 @@ public class VariableSymbolMarkupContentBuilder implements MarkupContentBuilder< private static final String VARIABLE_KEY = "var"; private static final String EXPORT_KEY = "export"; + private final TypeResolver typeResolver; private final LanguageServerConfiguration configuration; @Override @@ -69,6 +74,10 @@ public MarkupContent getContent(VariableSymbol symbol) { .map(VariableDescription::getPurposeDescription) .ifPresent(trailingDescription -> addSectionIfNotEmpty(markupBuilder, trailingDescription)); + var types = typeResolver.findTypes(symbol); + var typeDescription = getTypeDescription(types); + addSectionIfNotEmpty(markupBuilder, typeDescription); + String content = markupBuilder.toString(); return new MarkupContent(MarkupKind.MARKDOWN, content); @@ -112,6 +121,18 @@ private static String getLocation(VariableSymbol symbol) { ); } + private static String getTypeDescription(List types) { + var typeDescription = types.stream() + .map(Type::getName) + .collect(Collectors.joining(" | ")); + + if (!typeDescription.isEmpty()) { + typeDescription = "`" + typeDescription + "`"; + } + + return typeDescription; + } + private static void addSectionIfNotEmpty(StringJoiner markupBuilder, String newContent) { if (!newContent.isEmpty()) { markupBuilder.add(newContent); diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/Type.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/Type.java new file mode 100644 index 00000000000..2050fad7010 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/Type.java @@ -0,0 +1,29 @@ +/* + * 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.types; + +import lombok.Value; + +@Value +public class Type { + private final String name; +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java new file mode 100644 index 00000000000..b835642ef54 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java @@ -0,0 +1,181 @@ +/* + * 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.types; + +import com.github._1c_syntax.bsl.languageserver.context.ServerContext; +import com.github._1c_syntax.bsl.languageserver.context.symbol.Describable; +import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.variable.VariableDescription; +import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex; +import com.github._1c_syntax.bsl.languageserver.references.ReferenceResolver; +import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType; +import com.github._1c_syntax.bsl.languageserver.references.model.Reference; +import com.github._1c_syntax.bsl.languageserver.utils.Trees; +import com.github._1c_syntax.bsl.languageserver.utils.bsl.Constructors; +import com.github._1c_syntax.bsl.parser.BSLParser; +import lombok.RequiredArgsConstructor; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.eclipse.lsp4j.Position; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +@Component +@RequiredArgsConstructor +public class TypeResolver { + + private final ServerContext serverContext; + private final ReferenceResolver referenceResolver; + private final ReferenceIndex referenceIndex; + + // TODO: Create LRU cache for calculated types. + + // TODO: Use reference instead of symbol. Refactor hover provider to pass references to markup content builders. + public List findTypes(SourceDefinedSymbol symbol) { + return calculateTypes(symbol); + } + + public List findTypes(URI uri, Position position) { + return referenceResolver.findReference(uri, position) + .stream() + .flatMap(reference -> calculateTypes(uri, reference).stream()) + .distinct() + .toList(); + } + + private List calculateTypes(SourceDefinedSymbol symbol) { + + // variable description resolver + if (symbol instanceof Describable describableSymbol) { + var maybeDescription = describableSymbol.getDescription(); + if (maybeDescription.isPresent()) { + var description = maybeDescription.get(); + if (description instanceof VariableDescription variableDescription) { + // TODO: extract types from type description and return. + } + } + } + + // reference-based type resolver + var ast = symbol.getOwner().getAst(); + var position = symbol.getSelectionRange().getStart(); + + var typesOfCurrentReference = calculateTypes(ast, position); + + var typesOfOtherReferences = referenceIndex.getReferencesTo(symbol).stream() + .filter(referenceTo -> referenceTo.getOccurrenceType() == OccurrenceType.DEFINITION) + .map(referenceTo -> calculateTypes(ast, referenceTo.getSelectionRange().getStart())) + .flatMap(Collection::stream) + .toList(); + + return Stream.concat(typesOfCurrentReference.stream(), typesOfOtherReferences.stream()) + .distinct() + .toList(); + } + + private List calculateTypes(URI uri, Reference reference) { + + // source defined symbol resolver + if (reference.isSourceDefinedSymbolReference()) { + return calculateTypes(reference.getSourceDefinedSymbol().orElseThrow()); + } + + // expression tree resolver + if (reference.getOccurrenceType() == OccurrenceType.DEFINITION) { + var document = serverContext.getDocument(uri); + var ast = document.getAst(); + var position = reference.getSelectionRange().getStart(); + return calculateTypes(ast, position); + } + + // no-op + return Collections.emptyList(); + } + + private List calculateTypes(BSLParser.FileContext ast, Position position) { + return Trees.findTerminalNodeContainsPosition(ast, position) + .map(TerminalNode::getParent) + .map(ruleNode -> Trees.getRootParent(ruleNode, BSLParser.RULE_assignment)) + .map(BSLParser.AssignmentContext.class::cast) + .map(BSLParser.AssignmentContext::expression) + .map(this::calculateTypes) + .orElseGet(Collections::emptyList); + } + + private List calculateTypes(BSLParser.ExpressionContext expression) { + + // only simple cases for now. Use ExpressionTree in the future. + if (!expression.operation().isEmpty()) { + return Collections.emptyList(); + } + + // new-resolver + var typeName = typeName(expression); + if (!typeName.isEmpty()) { + Type type = new Type(typeName); + return List.of(type); + } + + // const-value resolver + var constValueContext = expression.member(0).constValue(); + if (constValueContext == null) { + return Collections.emptyList(); + } + + Type type = null; + if (constValueContext.DATETIME() != null) { + type = new Type("Дата"); + } else if (constValueContext.FALSE() != null || constValueContext.TRUE() != null) { + type = new Type("Булево"); + } else if (constValueContext.NULL() != null) { + type = new Type("null"); + } else if (constValueContext.numeric() != null) { + type = new Type("Число"); + } else if (constValueContext.string() != null) { + type = new Type("Строка"); + } else if (constValueContext.UNDEFINED() != null) { + type = new Type("Неопределено"); + } + + if (type != null) { + return List.of(type); + } + + return Collections.emptyList(); + + } + + private String typeName(BSLParser.ExpressionContext ctx) { + var typeName = ""; + var newCtx = Trees.getNextNode(ctx, ctx, BSLParser.RULE_newExpression); + if (newCtx instanceof BSLParser.NewExpressionContext newExpression) { + typeName = Constructors.typeName(newExpression).orElse(""); + } + return typeName; + } + + +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Trees.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Trees.java index e57dfa3a772..1230185798b 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Trees.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Trees.java @@ -28,6 +28,7 @@ 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.RuleNode; import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.v4.runtime.tree.Tree; import org.eclipse.lsp4j.Position; @@ -286,14 +287,14 @@ public static BSLParserRuleContext getRootParent(BSLParserRuleContext tnc) { * @return tnc - если родитель не найден, вернет null */ @Nullable - public static BSLParserRuleContext getRootParent(BSLParserRuleContext tnc, int ruleindex) { + public static BSLParserRuleContext getRootParent(RuleNode tnc, int ruleindex) { final var parent = tnc.getParent(); if (parent == null) { return null; } if (getRuleIndex(parent) == ruleindex) { - return parent; + return (BSLParserRuleContext) parent; } else { return getRootParent(parent, ruleindex); } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java new file mode 100644 index 00000000000..b08f2930c2e --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java @@ -0,0 +1,114 @@ +/* + * 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.types; + +import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import org.eclipse.lsp4j.Position; +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 +class TypeResolverTest { + + @Autowired + private TypeResolver typeResolver; + + @Test + void simpleType() { + // given + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + + // when + var types = typeResolver.findTypes(documentContext.getUri(), new Position(2, 10)); + + // then + assertThat(types).hasSize(1); + } + + @Test + void twoTypesOnReassignment() { + // given + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + + // when + var types = typeResolver.findTypes(documentContext.getUri(), new Position(5, 4)); + + // then + assertThat(types).hasSize(2); + } + + @Test + void twoTypesOnPlaceOfUsage() { + // given + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + + // when + var types = typeResolver.findTypes(documentContext.getUri(), new Position(7, 10)); + + // then + assertThat(types).hasSize(2); + } + + @Test + void twoTypesFromSymbol() { + // given + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("ДваТипа", documentContext.getSymbolTree().getModule()).orElseThrow(); + + // when + var types = typeResolver.findTypes(variableSymbol); + + // then + assertThat(types).hasSize(2); + } + + @Test + void twoAssignments() { + // given + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("Переприсваивание", documentContext.getSymbolTree().getModule()).orElseThrow(); + + // when + var types = typeResolver.findTypes(variableSymbol); + + // then + assertThat(types).hasSize(1); + } + + @Test + void array() { + // given + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("ДругоеИмяМассива", documentContext.getSymbolTree().getModule()).orElseThrow(); + + // when + var types = typeResolver.findTypes(variableSymbol); + + // then + assertThat(types).hasSize(1); + assertThat(types.get(0).getName()).isEqualTo("Массив"); + } + +} \ No newline at end of file diff --git a/src/test/resources/types/TypeResolver.os b/src/test/resources/types/TypeResolver.os new file mode 100644 index 00000000000..1ed212f057d --- /dev/null +++ b/src/test/resources/types/TypeResolver.os @@ -0,0 +1,16 @@ +ИмяКорневойАннотации = "Завязь"; + +Сообщить(ИмяКорневойАннотации); + +ДваТипа = "Строка"; +ДваТипа = 0; + +Сообщить(ДваТипа); + +Переприсваивание = 0; +Переприсваивание = 0; + +Сообщить(Переприсваивание); + +ДругоеИмяМассива = Новый Массив; +ДругоеИмяМассива.Добавить(1); From cdafb4216e655b0aaedd1a5a3514fcf524c3e9c3 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Wed, 24 Apr 2024 22:58:20 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=87=D0=B5=D1=82=20?= =?UTF-8?q?=D1=82=D0=B8=D0=BF=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0?= =?UTF-8?q?=D1=89=D0=B0=D0=B5=D0=BC=D0=BE=D0=B3=D0=BE=20=D0=B7=D0=BD=D0=B0?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0=20=D0=B8=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../languageserver/types/TypeResolver.java | 82 ++++++++++++++++--- .../types/TypeResolverTest.java | 36 +++++++- src/test/resources/types/TypeResolver.os | 18 ++++ 3 files changed, 122 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java index b835642ef54..18d99905174 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java @@ -24,11 +24,14 @@ import com.github._1c_syntax.bsl.languageserver.context.ServerContext; import com.github._1c_syntax.bsl.languageserver.context.symbol.Describable; import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.description.MethodDescription; +import com.github._1c_syntax.bsl.languageserver.context.symbol.description.TypeDescription; import com.github._1c_syntax.bsl.languageserver.context.symbol.variable.VariableDescription; import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex; import com.github._1c_syntax.bsl.languageserver.references.ReferenceResolver; import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType; import com.github._1c_syntax.bsl.languageserver.references.model.Reference; +import com.github._1c_syntax.bsl.languageserver.utils.Ranges; import com.github._1c_syntax.bsl.languageserver.utils.Trees; import com.github._1c_syntax.bsl.languageserver.utils.bsl.Constructors; import com.github._1c_syntax.bsl.parser.BSLParser; @@ -41,6 +44,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; import java.util.stream.Stream; @Component @@ -74,20 +79,34 @@ private List calculateTypes(SourceDefinedSymbol symbol) { if (maybeDescription.isPresent()) { var description = maybeDescription.get(); if (description instanceof VariableDescription variableDescription) { - // TODO: extract types from type description and return. + // TODO: use new type information from new bsp-parser + var purposeDescription = variableDescription.getPurposeDescription(); + var typeName = Pattern.compile("^(\\S+)").matcher(purposeDescription).results() + .findFirst() + .map(MatchResult::group) + .orElse(""); + + if (!typeName.isEmpty()) { + return List.of(new Type(typeName)); + } } } } // reference-based type resolver + var uri = symbol.getOwner().getUri(); var ast = symbol.getOwner().getAst(); + if (ast == null) { + return Collections.emptyList(); + } + var position = symbol.getSelectionRange().getStart(); - var typesOfCurrentReference = calculateTypes(ast, position); + var typesOfCurrentReference = calculateTypes(uri, ast, position); var typesOfOtherReferences = referenceIndex.getReferencesTo(symbol).stream() .filter(referenceTo -> referenceTo.getOccurrenceType() == OccurrenceType.DEFINITION) - .map(referenceTo -> calculateTypes(ast, referenceTo.getSelectionRange().getStart())) + .map(referenceTo -> calculateTypes(uri, ast, referenceTo.getSelectionRange().getStart())) .flatMap(Collection::stream) .toList(); @@ -107,25 +126,28 @@ private List calculateTypes(URI uri, Reference reference) { if (reference.getOccurrenceType() == OccurrenceType.DEFINITION) { var document = serverContext.getDocument(uri); var ast = document.getAst(); + if (ast == null) { + return Collections.emptyList(); + } var position = reference.getSelectionRange().getStart(); - return calculateTypes(ast, position); + return calculateTypes(uri, ast, position); } // no-op return Collections.emptyList(); } - private List calculateTypes(BSLParser.FileContext ast, Position position) { + private List calculateTypes(URI uri, BSLParser.FileContext ast, Position position) { return Trees.findTerminalNodeContainsPosition(ast, position) .map(TerminalNode::getParent) .map(ruleNode -> Trees.getRootParent(ruleNode, BSLParser.RULE_assignment)) .map(BSLParser.AssignmentContext.class::cast) .map(BSLParser.AssignmentContext::expression) - .map(this::calculateTypes) + .map(expression -> calculateTypes(uri, expression)) .orElseGet(Collections::emptyList); } - private List calculateTypes(BSLParser.ExpressionContext expression) { + private List calculateTypes(URI uri, BSLParser.ExpressionContext expression) { // only simple cases for now. Use ExpressionTree in the future. if (!expression.operation().isEmpty()) { @@ -133,12 +155,18 @@ private List calculateTypes(BSLParser.ExpressionContext expression) { } // new-resolver - var typeName = typeName(expression); + var typeName = newTypeName(expression); if (!typeName.isEmpty()) { Type type = new Type(typeName); return List.of(type); } + // globalMethodCall resolver + var typeNames = returnedValue(uri, expression); + if (!typeNames.isEmpty()) { + return typeNames; + } + // const-value resolver var constValueContext = expression.member(0).constValue(); if (constValueContext == null) { @@ -168,14 +196,48 @@ private List calculateTypes(BSLParser.ExpressionContext expression) { } - private String typeName(BSLParser.ExpressionContext ctx) { + private String newTypeName(BSLParser.ExpressionContext expression) { var typeName = ""; - var newCtx = Trees.getNextNode(ctx, ctx, BSLParser.RULE_newExpression); + var newCtx = Trees.getNextNode(expression, expression, BSLParser.RULE_newExpression); if (newCtx instanceof BSLParser.NewExpressionContext newExpression) { typeName = Constructors.typeName(newExpression).orElse(""); } return typeName; } + private List returnedValue(URI uri, BSLParser.ExpressionContext expression) { + var complexIdentifier = expression.member(0).complexIdentifier(); + + if (complexIdentifier == null) { + return Collections.emptyList(); + } + + if (!complexIdentifier.modifier().isEmpty()) { + return Collections.emptyList(); + } + + var globalMethodCall = complexIdentifier.globalMethodCall(); + + if (globalMethodCall == null) { + return Collections.emptyList(); + } + + var calledMethod = referenceResolver.findReference(uri, Ranges.create(globalMethodCall.methodName()).getStart()); + + return calledMethod.filter(Reference::isSourceDefinedSymbolReference) + .flatMap(Reference::getSourceDefinedSymbol) + .filter(Describable.class::isInstance) + .map(Describable.class::cast) + .flatMap(Describable::getDescription) + .filter(MethodDescription.class::isInstance) + .map(MethodDescription.class::cast) + .map(MethodDescription::getReturnedValue) + .stream() + .flatMap(List::stream) + .map(TypeDescription::getName) + .map(Type::new) + .toList(); + + } } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java index b08f2930c2e..8d0c0f0519f 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java @@ -41,7 +41,7 @@ void simpleType() { var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); // when - var types = typeResolver.findTypes(documentContext.getUri(), new Position(2, 10)); + var types = typeResolver.findTypes(documentContext.getUri(), new Position(5, 10)); // then assertThat(types).hasSize(1); @@ -53,7 +53,7 @@ void twoTypesOnReassignment() { var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); // when - var types = typeResolver.findTypes(documentContext.getUri(), new Position(5, 4)); + var types = typeResolver.findTypes(documentContext.getUri(), new Position(8, 4)); // then assertThat(types).hasSize(2); @@ -65,7 +65,7 @@ void twoTypesOnPlaceOfUsage() { var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); // when - var types = typeResolver.findTypes(documentContext.getUri(), new Position(7, 10)); + var types = typeResolver.findTypes(documentContext.getUri(), new Position(10, 10)); // then assertThat(types).hasSize(2); @@ -98,7 +98,7 @@ void twoAssignments() { } @Test - void array() { + void newArray() { // given var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("ДругоеИмяМассива", documentContext.getSymbolTree().getModule()).orElseThrow(); @@ -111,4 +111,32 @@ void array() { assertThat(types.get(0).getName()).isEqualTo("Массив"); } + @Test + void globalMethodCall() { + // given + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("РезультатФункции", documentContext.getSymbolTree().getModule()).orElseThrow(); + + // when + var types = typeResolver.findTypes(variableSymbol); + + // then + assertThat(types).hasSize(1); + assertThat(types.get(0).getName()).isEqualTo("Строка"); + } + + @Test + void varWithDescription() { + // given + var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("ПеременнаяСОписанием", documentContext.getSymbolTree().getModule()).orElseThrow(); + + // when + var types = typeResolver.findTypes(variableSymbol); + + // then + assertThat(types).hasSize(1); + assertThat(types.get(0).getName()).isEqualTo("Строка"); + } + } \ No newline at end of file diff --git a/src/test/resources/types/TypeResolver.os b/src/test/resources/types/TypeResolver.os index 1ed212f057d..f3f37b4ee24 100644 --- a/src/test/resources/types/TypeResolver.os +++ b/src/test/resources/types/TypeResolver.os @@ -1,3 +1,6 @@ +// Строка - какая-то переменная +Перем ПеременнаяСОписанием; + ИмяКорневойАннотации = "Завязь"; Сообщить(ИмяКорневойАннотации); @@ -14,3 +17,18 @@ ДругоеИмяМассива = Новый Массив; ДругоеИмяМассива.Добавить(1); + +РезультатФункции = КакаяТоФункция(); + +Сообщить(РезультатФункции); + +Сообщить(ПеременнаяСОписанием); + +// Важная функция +// +// Возвращаемое значение: +// Строка - что-то там. +// +Функция КакаяТоФункция() + Возврат "Какая-то функция"; +КонецФункции \ No newline at end of file From 243c8ff0d2df9d7ebc9b73c71f1bf592e2143792 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Thu, 25 Apr 2024 08:13:36 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D1=81=20=D1=85=D0=BE=D0=B2=D0=B5=D1=80=D0=BE=D0=BC=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B0=20reference-api.=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D0=B5=20NPE=20=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=B8=20=D1=82=D0=B8=D0=BF=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D1=81=D0=B8=D0=BC=D0=B2=D0=BE=D0=BB=D1=83.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hover/MarkupContentBuilder.java | 6 ++-- .../MethodSymbolMarkupContentBuilder.java | 3 +- .../VariableSymbolMarkupContentBuilder.java | 5 +-- .../providers/HoverProvider.java | 2 +- .../languageserver/types/TypeResolver.java | 28 +++++++++++----- .../MethodSymbolMarkupContentBuilderTest.java | 22 +++++++++++-- ...ariableSymbolMarkupContentBuilderTest.java | 28 +++++++++++++--- .../types/TypeResolverTest.java | 32 +++++++++++++++---- 8 files changed, 98 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/MarkupContentBuilder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/MarkupContentBuilder.java index 8ef2864767f..d63624dfa84 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/MarkupContentBuilder.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/MarkupContentBuilder.java @@ -22,6 +22,7 @@ package com.github._1c_syntax.bsl.languageserver.hover; import com.github._1c_syntax.bsl.languageserver.context.symbol.Symbol; +import com.github._1c_syntax.bsl.languageserver.references.model.Reference; import org.eclipse.lsp4j.MarkupContent; import org.eclipse.lsp4j.SymbolKind; @@ -34,10 +35,11 @@ public interface MarkupContentBuilder { /** * Возвращает контент для всплывающего окна на основе символа. * - * @param symbol Символ, для которого нужно построить контент. + * @param reference Ссылка на символ, для которого нужно построить контент. + * @param symbol Символ с приведенным типом, для которого нужно построить контент. * @return Сконструированный контент. */ - MarkupContent getContent(T symbol); + MarkupContent getContent(Reference reference, T symbol); /** * Тип символа, на основе которого работает данный построитель. 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..f41e4c6eaec 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 @@ -27,6 +27,7 @@ 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.references.model.Reference; import com.github._1c_syntax.bsl.languageserver.utils.MdoRefBuilder; import com.github._1c_syntax.bsl.languageserver.utils.Resources; import lombok.RequiredArgsConstructor; @@ -63,7 +64,7 @@ public class MethodSymbolMarkupContentBuilder implements MarkupContentBuilder addSectionIfNotEmpty(markupBuilder, trailingDescription)); - var types = typeResolver.findTypes(symbol); + var types = typeResolver.findTypes(reference); var typeDescription = getTypeDescription(types); addSectionIfNotEmpty(markupBuilder, typeDescription); diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/HoverProvider.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/HoverProvider.java index 2d180202559..cc11646e0d9 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/HoverProvider.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/HoverProvider.java @@ -52,7 +52,7 @@ public Optional getHover(DocumentContext documentContext, HoverParams par var range = reference.getSelectionRange(); return Optional.ofNullable(markupContentBuilders.get(symbol.getSymbolKind())) - .map(markupContentBuilder -> markupContentBuilder.getContent(symbol)) + .map(markupContentBuilder -> markupContentBuilder.getContent(reference, symbol)) .map(content -> new Hover(content, range)); }); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java index 18d99905174..a0bfe99bc95 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolver.java @@ -24,6 +24,7 @@ import com.github._1c_syntax.bsl.languageserver.context.ServerContext; import com.github._1c_syntax.bsl.languageserver.context.symbol.Describable; import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.Symbol; import com.github._1c_syntax.bsl.languageserver.context.symbol.description.MethodDescription; import com.github._1c_syntax.bsl.languageserver.context.symbol.description.TypeDescription; import com.github._1c_syntax.bsl.languageserver.context.symbol.variable.VariableDescription; @@ -58,20 +59,26 @@ public class TypeResolver { // TODO: Create LRU cache for calculated types. - // TODO: Use reference instead of symbol. Refactor hover provider to pass references to markup content builders. - public List findTypes(SourceDefinedSymbol symbol) { - return calculateTypes(symbol); + public List findTypes(Reference reference) { + return calculateTypes(reference.getUri(), reference.getSymbol()); } public List findTypes(URI uri, Position position) { return referenceResolver.findReference(uri, position) .stream() - .flatMap(reference -> calculateTypes(uri, reference).stream()) + .flatMap(reference -> calculateTypes(reference).stream()) .distinct() .toList(); } - private List calculateTypes(SourceDefinedSymbol symbol) { + private List calculateTypes(URI uri, Symbol symbol) { + if (symbol instanceof SourceDefinedSymbol sourceDefinedSymbol) { + return calculateTypes(uri, sourceDefinedSymbol); + } + return Collections.emptyList(); + } + + private List calculateTypes(URI uri, SourceDefinedSymbol symbol) { // variable description resolver if (symbol instanceof Describable describableSymbol) { @@ -94,7 +101,9 @@ private List calculateTypes(SourceDefinedSymbol symbol) { } // reference-based type resolver - var uri = symbol.getOwner().getUri(); + if (symbol.getOwner().getContent() == null) { + return Collections.emptyList(); + } var ast = symbol.getOwner().getAst(); if (ast == null) { return Collections.emptyList(); @@ -115,13 +124,16 @@ private List calculateTypes(SourceDefinedSymbol symbol) { .toList(); } - private List calculateTypes(URI uri, Reference reference) { + private List calculateTypes(Reference reference) { + + var uri = reference.getUri(); // source defined symbol resolver if (reference.isSourceDefinedSymbolReference()) { - return calculateTypes(reference.getSourceDefinedSymbol().orElseThrow()); + return calculateTypes(uri, reference.getSourceDefinedSymbol().orElseThrow()); } + // expression tree resolver if (reference.getOccurrenceType() == OccurrenceType.DEFINITION) { var document = serverContext.getDocument(uri); diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilderTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilderTest.java index da295eb3092..c1902954090 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilderTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/MethodSymbolMarkupContentBuilderTest.java @@ -21,11 +21,16 @@ */ package com.github._1c_syntax.bsl.languageserver.hover; +import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; import com.github._1c_syntax.bsl.languageserver.context.ServerContext; +import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol; +import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType; +import com.github._1c_syntax.bsl.languageserver.references.model.Reference; import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterClass; import com.github._1c_syntax.bsl.languageserver.util.TestUtils; import com.github._1c_syntax.bsl.types.ModuleType; import jakarta.annotation.PostConstruct; +import org.eclipse.lsp4j.Location; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -59,9 +64,10 @@ void testContentFromDirectFile() { // given var documentContext = TestUtils.getDocumentContextFromFile(PATH_TO_FILE); var methodSymbol = documentContext.getSymbolTree().getMethodSymbol("ИмяФункции").orElseThrow(); + var reference = getReference(documentContext, methodSymbol); // when - var content = markupContentBuilder.getContent(methodSymbol).getValue(); + var content = markupContentBuilder.getContent(reference, methodSymbol).getValue(); assertThat(content).isNotEmpty(); @@ -122,9 +128,10 @@ void testContentFromManagerModule() { // given var documentContext = serverContext.getDocument("Catalog.Справочник1", ModuleType.ManagerModule).orElseThrow(); var methodSymbol = documentContext.getSymbolTree().getMethodSymbol("ТестЭкспортная").orElseThrow(); + var reference = getReference(documentContext, methodSymbol); // when - var content = markupContentBuilder.getContent(methodSymbol).getValue(); + var content = markupContentBuilder.getContent(reference, methodSymbol).getValue(); // then assertThat(content).isNotEmpty(); @@ -146,9 +153,10 @@ void testMethodsFromCommonModule() { // given var documentContext = serverContext.getDocument("CommonModule.ПервыйОбщийМодуль", ModuleType.CommonModule).orElseThrow(); var methodSymbol = documentContext.getSymbolTree().getMethodSymbol("УстаревшаяПроцедура").orElseThrow(); + var reference = getReference(documentContext, methodSymbol); // when - var content = markupContentBuilder.getContent(methodSymbol).getValue(); + var content = markupContentBuilder.getContent(reference, methodSymbol).getValue(); // then assertThat(content).isNotEmpty(); @@ -166,4 +174,12 @@ void testMethodsFromCommonModule() { assertThat(blocks.get(2)).isEqualTo("Процедура - Устаревшая процедура\n\n"); } + private static Reference getReference(DocumentContext documentContext, MethodSymbol methodSymbol) { + return Reference.of( + documentContext.getSymbolTree().getModule(), + methodSymbol, + new Location(documentContext.getUri().toString(), methodSymbol.getSelectionRange()), + OccurrenceType.DEFINITION + ); + } } \ No newline at end of file diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilderTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilderTest.java index b8ade4cb595..4f501ddb472 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilderTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/hover/VariableSymbolMarkupContentBuilderTest.java @@ -21,11 +21,16 @@ */ package com.github._1c_syntax.bsl.languageserver.hover; +import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; import com.github._1c_syntax.bsl.languageserver.context.ServerContext; +import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol; +import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType; +import com.github._1c_syntax.bsl.languageserver.references.model.Reference; import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterClass; import com.github._1c_syntax.bsl.languageserver.util.TestUtils; import com.github._1c_syntax.bsl.types.ModuleType; import jakarta.annotation.PostConstruct; +import org.eclipse.lsp4j.Location; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -60,9 +65,10 @@ void testFileVarContentFromDirectFile_NoComments() { var documentContext = TestUtils.getDocumentContextFromFile(PATH_TO_FILE); final var symbolTree = documentContext.getSymbolTree(); var varSymbol = symbolTree.getVariableSymbol("ИмяБезОписания", symbolTree.getModule()).orElseThrow(); + var reference = getReference(documentContext, varSymbol); // when - var content = markupContentBuilder.getContent(varSymbol).getValue(); + var content = markupContentBuilder.getContent(reference, varSymbol).getValue(); assertThat(content).isNotEmpty(); @@ -87,9 +93,10 @@ void testFileVarContentFromDirectFile_OneCommentsStringFromRight() { var documentContext = TestUtils.getDocumentContextFromFile(PATH_TO_FILE); final var symbolTree = documentContext.getSymbolTree(); var varSymbol = symbolTree.getVariableSymbol("Имя_ОписаниеСправаОднойСтрокой", symbolTree.getModule()).orElseThrow(); + var reference = getReference(documentContext, varSymbol); // when - var content = markupContentBuilder.getContent(varSymbol).getValue(); + var content = markupContentBuilder.getContent(reference, varSymbol).getValue(); assertThat(content).isNotEmpty(); @@ -119,9 +126,10 @@ void testMethodVarContentFromDirectFile_2_comments_strings() { final var symbolTree = documentContext.getSymbolTree(); var methodSymbol = symbolTree.getMethodSymbol("ИмяФункции").orElseThrow(); var varSymbol = symbolTree.getVariableSymbol("Имя_ОписаниеСверхуДвеСтроки_Функция", methodSymbol).orElseThrow(); + var reference = getReference(documentContext, varSymbol); // when - var content = markupContentBuilder.getContent(varSymbol).getValue(); + var content = markupContentBuilder.getContent(reference, varSymbol).getValue(); assertThat(content).isNotEmpty(); @@ -153,9 +161,10 @@ void testMethodVarContentFromDirectFile_3_comments_strings() { final var symbolTree = documentContext.getSymbolTree(); var methodSymbol = symbolTree.getMethodSymbol("ИмяФункции").orElseThrow(); var varSymbol = symbolTree.getVariableSymbol("Имя_ОписаниеСверхуТриСтрокиПоследняяПустая_Функция", methodSymbol).orElseThrow(); + var reference = getReference(documentContext, varSymbol); // when - var content = markupContentBuilder.getContent(varSymbol).getValue(); + var content = markupContentBuilder.getContent(reference, varSymbol).getValue(); assertThat(content).isNotEmpty(); @@ -186,9 +195,10 @@ void testContentFromObjectModule() { var documentContext = serverContext.getDocument("Catalog.Справочник1", ModuleType.ObjectModule).orElseThrow(); final var symbolTree = documentContext.getSymbolTree(); var varSymbol = symbolTree.getVariableSymbol("ВалютаУчета", symbolTree.getModule()).orElseThrow(); + var reference = getReference(documentContext, varSymbol); // when - var content = markupContentBuilder.getContent(varSymbol).getValue(); + var content = markupContentBuilder.getContent(reference, varSymbol).getValue(); // then assertThat(content).isNotEmpty(); @@ -205,4 +215,12 @@ void testContentFromObjectModule() { assertThat(blocks.get(1)).matches("\\[Catalog.Справочник1]\\(.*Catalogs/.*/Ext/ObjectModule.bsl#\\d+\\)\n\n"); } + private static Reference getReference(DocumentContext documentContext, VariableSymbol variableSymbol) { + return Reference.of( + documentContext.getSymbolTree().getModule(), + variableSymbol, + new Location(documentContext.getUri().toString(), variableSymbol.getSelectionRange()), + OccurrenceType.DEFINITION + ); + } } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java index 8d0c0f0519f..bb4c8617e8c 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/types/TypeResolverTest.java @@ -21,7 +21,12 @@ */ package com.github._1c_syntax.bsl.languageserver.types; +import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; +import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol; +import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType; +import com.github._1c_syntax.bsl.languageserver.references.model.Reference; import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Position; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +40,8 @@ class TypeResolverTest { @Autowired private TypeResolver typeResolver; + public static final String PATH_TO_FILE = "./src/test/resources/types/TypeResolver.os"; + @Test void simpleType() { // given @@ -76,9 +83,10 @@ void twoTypesFromSymbol() { // given var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("ДваТипа", documentContext.getSymbolTree().getModule()).orElseThrow(); + var reference = getReference(documentContext, variableSymbol); // when - var types = typeResolver.findTypes(variableSymbol); + var types = typeResolver.findTypes(reference); // then assertThat(types).hasSize(2); @@ -89,9 +97,10 @@ void twoAssignments() { // given var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("Переприсваивание", documentContext.getSymbolTree().getModule()).orElseThrow(); + var reference = getReference(documentContext, variableSymbol); // when - var types = typeResolver.findTypes(variableSymbol); + var types = typeResolver.findTypes(reference); // then assertThat(types).hasSize(1); @@ -102,9 +111,10 @@ void newArray() { // given var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("ДругоеИмяМассива", documentContext.getSymbolTree().getModule()).orElseThrow(); + var reference = getReference(documentContext, variableSymbol); // when - var types = typeResolver.findTypes(variableSymbol); + var types = typeResolver.findTypes(reference); // then assertThat(types).hasSize(1); @@ -116,9 +126,10 @@ void globalMethodCall() { // given var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("РезультатФункции", documentContext.getSymbolTree().getModule()).orElseThrow(); + var reference = getReference(documentContext, variableSymbol); // when - var types = typeResolver.findTypes(variableSymbol); + var types = typeResolver.findTypes(reference); // then assertThat(types).hasSize(1); @@ -128,15 +139,24 @@ void globalMethodCall() { @Test void varWithDescription() { // given - var documentContext = TestUtils.getDocumentContextFromFile("./src/test/resources/types/TypeResolver.os"); + var documentContext = TestUtils.getDocumentContextFromFile(PATH_TO_FILE); var variableSymbol = documentContext.getSymbolTree().getVariableSymbol("ПеременнаяСОписанием", documentContext.getSymbolTree().getModule()).orElseThrow(); + var reference = getReference(documentContext, variableSymbol); // when - var types = typeResolver.findTypes(variableSymbol); + var types = typeResolver.findTypes(reference); // then assertThat(types).hasSize(1); assertThat(types.get(0).getName()).isEqualTo("Строка"); } + private static Reference getReference(DocumentContext documentContext, VariableSymbol variableSymbol) { + return Reference.of( + documentContext.getSymbolTree().getModule(), + variableSymbol, + new Location(documentContext.getUri().toString(), variableSymbol.getSelectionRange()), + OccurrenceType.DEFINITION + ); + } } \ No newline at end of file From 3cb6bbfe3ecd5a351299084fcb78c08be189f5e8 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Tue, 4 Jun 2024 20:35:20 +0200 Subject: [PATCH 4/4] draft of completion provider --- .../bsl/languageserver/BSLLanguageServer.java | 11 +++ .../BSLTextDocumentService.java | 18 ++++ .../providers/CompletionProvider.java | 86 +++++++++++++++++++ .../bsl/languageserver/types/KnownTypes.java | 39 +++++++++ .../providers/CompletionProviderTest.java | 57 ++++++++++++ ...20\232\320\273\320\260\321\201\321\201.os" | 17 ++++ src/test/resources/providers/completion.os | 4 + 7 files changed, 232 insertions(+) create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/CompletionProvider.java create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/types/KnownTypes.java create mode 100644 src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/CompletionProviderTest.java create mode 100644 "src/test/resources/metadata/oslib/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\271\320\232\320\273\320\260\321\201\321\201.os" create mode 100644 src/test/resources/providers/completion.os diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLLanguageServer.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLLanguageServer.java index 2be3d0562e6..1f67d797325 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLLanguageServer.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLLanguageServer.java @@ -37,6 +37,7 @@ import org.eclipse.lsp4j.CodeActionOptions; import org.eclipse.lsp4j.CodeLensOptions; import org.eclipse.lsp4j.ColorProviderOptions; +import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.DefinitionOptions; import org.eclipse.lsp4j.DocumentFormattingOptions; import org.eclipse.lsp4j.DocumentLinkOptions; @@ -123,6 +124,7 @@ public CompletableFuture initialize(InitializeParams params) { capabilities.setRenameProvider(getRenameProvider(params)); capabilities.setInlayHintProvider(getInlayHintProvider()); capabilities.setExecuteCommandProvider(getExecuteCommandProvider()); + capabilities.setCompletionProvider(getCompletionProvider()); var result = new InitializeResult(capabilities, serverInfo); @@ -336,4 +338,13 @@ private ExecuteCommandOptions getExecuteCommandProvider() { executeCommandOptions.setWorkDoneProgress(Boolean.FALSE); return executeCommandOptions; } + + private CompletionOptions getCompletionProvider() { + var completionOptions = new CompletionOptions(); + completionOptions.setTriggerCharacters(List.of(".")); + completionOptions.setResolveProvider(Boolean.FALSE); + completionOptions.setWorkDoneProgress(Boolean.FALSE); + + return completionOptions; + } } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java index b2e534bd14b..0ef82af2794 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java @@ -32,6 +32,7 @@ import com.github._1c_syntax.bsl.languageserver.providers.CodeActionProvider; import com.github._1c_syntax.bsl.languageserver.providers.CodeLensProvider; import com.github._1c_syntax.bsl.languageserver.providers.ColorProvider; +import com.github._1c_syntax.bsl.languageserver.providers.CompletionProvider; import com.github._1c_syntax.bsl.languageserver.providers.DefinitionProvider; import com.github._1c_syntax.bsl.languageserver.providers.DiagnosticProvider; import com.github._1c_syntax.bsl.languageserver.providers.DocumentLinkProvider; @@ -60,6 +61,9 @@ import org.eclipse.lsp4j.ColorPresentation; import org.eclipse.lsp4j.ColorPresentationParams; import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionList; +import org.eclipse.lsp4j.CompletionParams; import org.eclipse.lsp4j.DefinitionParams; import org.eclipse.lsp4j.DidChangeTextDocumentParams; import org.eclipse.lsp4j.DidCloseTextDocumentParams; @@ -125,6 +129,7 @@ public class BSLTextDocumentService implements TextDocumentService, ProtocolExte private final ColorProvider colorProvider; private final RenameProvider renameProvider; private final InlayHintProvider inlayHintProvider; + private final CompletionProvider completionProvider; private final ExecutorService executorService = Executors.newCachedThreadPool(new CustomizableThreadFactory("text-document-service-")); @@ -370,6 +375,19 @@ public CompletableFuture> inlayHint(InlayHintParams params) { ); } + @Override + public CompletableFuture, CompletionList>> completion(CompletionParams params) { + var documentContext = context.getDocument(params.getTextDocument().getUri()); + if (documentContext == null) { + return CompletableFuture.completedFuture(null); + } + + return CompletableFuture.supplyAsync( + () -> completionProvider.getCompletions(documentContext, params), + executorService + ); + } + @Override public void didOpen(DidOpenTextDocumentParams params) { var textDocumentItem = params.getTextDocument(); diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/CompletionProvider.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/CompletionProvider.java new file mode 100644 index 00000000000..998a07f5250 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/CompletionProvider.java @@ -0,0 +1,86 @@ +package com.github._1c_syntax.bsl.languageserver.providers; + +import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; +import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.Symbol; +import com.github._1c_syntax.bsl.languageserver.types.KnownTypes; +import com.github._1c_syntax.bsl.languageserver.types.TypeResolver; +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.BSLLexer; +import lombok.RequiredArgsConstructor; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.CompletionList; +import org.eclipse.lsp4j.CompletionParams; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class CompletionProvider { + + private final TypeResolver typeResolver; + private final KnownTypes knownTypes; + + public Either, CompletionList> getCompletions(DocumentContext documentContext, CompletionParams params) { + + var completionList = new CompletionList(); + + var position = params.getPosition(); + var terminalNode = Trees.findTerminalNodeContainsPosition(documentContext.getAst(), position).orElseThrow(); + + if (terminalNode.getSymbol().getType() != BSLLexer.DOT) { + return Either.forRight(completionList); + } + + var previousToken = Trees.getPreviousTokenFromDefaultChannel(documentContext.getTokens(), terminalNode.getSymbol().getTokenIndex() - 1); + var completionItems = previousToken + .map(Ranges::create) + .map(Range::getStart) + .map(previousTokenPosition -> typeResolver.findTypes(documentContext.getUri(), previousTokenPosition)) + .stream() + .flatMap(Collection::stream) + .map(knownTypes::getSymbolByType) + .filter(Optional::isPresent) + .map(Optional::get) + .flatMap(symbol -> getChildren(symbol).stream()) + .map(symbol -> { + var completionItem = new CompletionItem(); + completionItem.setLabel(symbol.getName()); + completionItem.setKind(getCompletionItemKind(symbol.getSymbolKind())); + + return completionItem; + }) + .toList(); + + completionList.setItems(completionItems); + + return Either.forRight(completionList); + } + + private CompletionItemKind getCompletionItemKind(SymbolKind symbolKind) { + return switch (symbolKind) { + case Class -> CompletionItemKind.Class; + case Method -> CompletionItemKind.Method; + case Variable -> CompletionItemKind.Variable; + case Module -> CompletionItemKind.Module; + default -> CompletionItemKind.Text; + }; + } + + private List getChildren(Symbol symbol) { + if (!(symbol instanceof SourceDefinedSymbol sourceDefinedSymbol)) { + return Collections.emptyList(); + } + + return sourceDefinedSymbol.getChildren(); + } +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/KnownTypes.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/KnownTypes.java new file mode 100644 index 00000000000..dd2a83ef352 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/KnownTypes.java @@ -0,0 +1,39 @@ +package com.github._1c_syntax.bsl.languageserver.types; + +import com.github._1c_syntax.bsl.languageserver.context.events.DocumentContextContentChangedEvent; +import com.github._1c_syntax.bsl.languageserver.context.symbol.Symbol; +import org.apache.commons.io.FilenameUtils; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class KnownTypes { + + private final Map knownTypes = new ConcurrentHashMap<>(); + + public void addType(Type type, Symbol symbol) { + knownTypes.put(type, symbol); + } + + public Optional getSymbolByType(Type type) { + return Optional.ofNullable(knownTypes.get(type)); + } + + public void clear() { + knownTypes.clear(); + } + + @EventListener + public void handleEvent(DocumentContextContentChangedEvent event) { + var documentContext = event.getSource(); + // TODO: this logic should be moved to somewhere else. It will break for BSL files. + var typeName = FilenameUtils.getBaseName(documentContext.getUri().getPath()); + var module = documentContext.getSymbolTree().getModule(); + + addType(new Type(typeName), module); + } +} diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/CompletionProviderTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/CompletionProviderTest.java new file mode 100644 index 00000000000..fa6e042e65b --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/CompletionProviderTest.java @@ -0,0 +1,57 @@ +package com.github._1c_syntax.bsl.languageserver.providers; + +import com.github._1c_syntax.bsl.languageserver.context.ServerContext; +import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterClass; +import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import jakarta.annotation.PostConstruct; +import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.CompletionParams; +import org.eclipse.lsp4j.Position; +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 static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +@CleanupContextBeforeClassAndAfterClass +class CompletionProviderTest { + + @Autowired + private CompletionProvider completionProvider; + + @Autowired + private ServerContext serverContext; + + private static final String PATH_TO_FILE = "./src/test/resources/providers/completion.os"; + + @PostConstruct + void prepareServerContext() { + serverContext.setConfigurationRoot(Paths.get("src/test/resources/metadata/oslib")); + serverContext.populateContext(); + } + + @Test + void completionAfterDotOnOSClass() { + // given + var documentContext = TestUtils.getDocumentContextFromFile(PATH_TO_FILE); + + var params = new CompletionParams(); + params.setPosition(new Position(3, 13)); + + // when + var completions = completionProvider.getCompletions(documentContext, params); + + // then + assertTrue(completions.isRight()); + + var completionList = completions.getRight(); + assertThat(completionList.getItems()).hasSize(1); + assertThat(completionList.getItems().get(0).getLabel()).isEqualTo("NewObject"); + assertThat(completionList.getItems().get(0).getKind()).isEqualTo(CompletionItemKind.Method); + } + +} \ No newline at end of file diff --git "a/src/test/resources/metadata/oslib/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\271\320\232\320\273\320\260\321\201\321\201.os" "b/src/test/resources/metadata/oslib/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\271\320\232\320\273\320\260\321\201\321\201.os" new file mode 100644 index 00000000000..86aa12c6c29 --- /dev/null +++ "b/src/test/resources/metadata/oslib/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\271\320\232\320\273\320\260\321\201\321\201.os" @@ -0,0 +1,17 @@ +Перем ЭкспортнаяПеременная Экспорт; +Перем НеЭкспортнаяПеременная; + +Функция ЭкспортнаяФункция() Экспорт + Возврат ""; +КонецФункции + +Функция НеЭкспортнаяФункция() + Возврат ""; +КонецФункции + +Процедура ЭкспортнаяПроцедура() Экспорт +КонецПроцедуры + +Процедура НеЭкспортнаяПроцедура() +КонецПроцедуры + diff --git a/src/test/resources/providers/completion.os b/src/test/resources/providers/completion.os new file mode 100644 index 00000000000..b5109c7caa1 --- /dev/null +++ b/src/test/resources/providers/completion.os @@ -0,0 +1,4 @@ +#Использовать "metadata/oslib" + +КакойТоКласс = Новый МойКласс(); +КакойТоКласс.