diff --git a/src/main/antlr/BSLDescriptionParser.g4 b/src/main/antlr/BSLDescriptionParser.g4 index 17d6d318..fa9bf862 100644 --- a/src/main/antlr/BSLDescriptionParser.g4 +++ b/src/main/antlr/BSLDescriptionParser.g4 @@ -145,7 +145,7 @@ typesBlock: splitter type ; type: listTypes | collectionType | hyperlinkType | simpleType; -simpleType: typeName=(WORD | DOTSWORD) colon=COLON?; +simpleType: typeName=(WORD | DOTSWORD) (colon=COLON (SPACE? hyperlink)?)?; collectionType: collection=(WORD | DOTSWORD) SPACE OF_KEYWORD SPACE value=type; hyperlinkType: hyperlink; listTypes: listType (COMMA SPACE? listType?)+; diff --git a/src/main/java/com/github/_1c_syntax/bsl/parser/description/SimpleTypeDescription.java b/src/main/java/com/github/_1c_syntax/bsl/parser/description/SimpleTypeDescription.java index ec24fd4e..768271e9 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/parser/description/SimpleTypeDescription.java +++ b/src/main/java/com/github/_1c_syntax/bsl/parser/description/SimpleTypeDescription.java @@ -22,15 +22,18 @@ package com.github._1c_syntax.bsl.parser.description; import com.github._1c_syntax.bsl.parser.description.support.DescriptionElement; +import com.github._1c_syntax.bsl.parser.description.support.Hyperlink; import com.github._1c_syntax.bsl.parser.description.support.SimpleRange; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Value; import lombok.experimental.Accessors; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.List; + /** * Описание простого типа */ @@ -41,7 +44,8 @@ public class SimpleTypeDescription implements TypeDescription { "", "", Collections.emptyList(), - new DescriptionElement(SimpleRange.EMPTY, DescriptionElement.Type.UNKNOWN) + new DescriptionElement(SimpleRange.EMPTY, DescriptionElement.Type.UNKNOWN), + null ); @Accessors(fluent = true) @@ -56,10 +60,25 @@ public class SimpleTypeDescription implements TypeDescription { @Accessors(fluent = true) DescriptionElement element; + /** + * Ссылка, уточняющая тип; {@code null}, если тип ссылкой не уточнён + */ + @Nullable + @Accessors(fluent = true) + Hyperlink hyperlink; + public static TypeDescription create(String name, DescriptionElement element, String description, List fieldList) { + return create(name, element, description, fieldList, null); + } + + public static TypeDescription create(String name, + DescriptionElement element, + String description, + List fieldList, + @Nullable Hyperlink hyperlink) { if (name.isBlank() && description.isBlank()) { return EMPTY; } @@ -67,7 +86,8 @@ public static TypeDescription create(String name, name.strip().intern(), description.strip(), fieldList, - element + element, + hyperlink ); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/parser/description/TypeDescription.java b/src/main/java/com/github/_1c_syntax/bsl/parser/description/TypeDescription.java index c20a1dff..959ce9f7 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/parser/description/TypeDescription.java +++ b/src/main/java/com/github/_1c_syntax/bsl/parser/description/TypeDescription.java @@ -23,6 +23,9 @@ import com.github._1c_syntax.bsl.parser.description.support.DescriptionElement; +import com.github._1c_syntax.bsl.parser.description.support.Hyperlink; +import org.jspecify.annotations.Nullable; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -66,6 +69,18 @@ public interface TypeDescription { */ DescriptionElement element(); + /** + * Ссылка типа: у гиперссылочного типа это он сам, у простого — уточнение записью вида + * {@code СтрокаТабличнойЧасти: См. Справочник.Товары.ЕдиницыИзмерения}, где имя типа + * говорит, чем значение является, а ссылка — откуда взять его состав. + * + * @return Ссылка; {@code null}, если у типа ссылки нет + */ + @Nullable + default Hyperlink hyperlink() { + return null; + } + /** * Список элементов описания включая все дочерние описания (поля, типы...) * diff --git a/src/main/java/com/github/_1c_syntax/bsl/parser/description/reader/MethodDescriptionReader.java b/src/main/java/com/github/_1c_syntax/bsl/parser/description/reader/MethodDescriptionReader.java index 7ce52892..fb919df3 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/parser/description/reader/MethodDescriptionReader.java +++ b/src/main/java/com/github/_1c_syntax/bsl/parser/description/reader/MethodDescriptionReader.java @@ -465,6 +465,7 @@ private void addType(BSLDescriptionParser.SimpleTypeContext typeContext, BSLDescriptionParser.@Nullable TypeDescriptionContext description) { if (typeContext.typeName != null) { var lastType = new TempParameterTypeData(typeContext.typeName, TypeDescription.Variant.SIMPLE, level); + lastType.setHyperlink(typeContext.hyperlink()); if (description != null) { lastType.addTypeDescription(description); } @@ -525,6 +526,8 @@ private static final class TempParameterTypeData { private final TypeDescription.Variant variant; private final List valueTypes; private @Nullable Token linkParamsToken; + private @Nullable Token hyperlinkToken; + private @Nullable Token hyperlinkParamsToken; private final SimpleRange range; @@ -552,6 +555,35 @@ private TempParameterTypeData(Token typeName, TypeDescription.Variant variant, i this.name = typeName.getText(); } + /** + * Запомнить ссылку, уточняющую тип: {@code СтрокаТабличнойЧасти: См. Справочник.Товары.ЕдиницыИзмерения}. + * + * @param hyperlink Узел ссылки + */ + /** + * Собрать ссылку, уточняющую тип. + * + * @param lineShift Сдвиг номера строки + * @param charShifts Сдвиг символов по строкам + * + * @return Ссылка; {@code null}, если тип ссылкой не уточнён + */ + private @Nullable Hyperlink buildHyperlink(int lineShift, int[] charShifts) { + if (hyperlinkToken == null) { + return null; + } + var params = hyperlinkParamsToken == null ? "" : hyperlinkParamsToken.getText(); + return Hyperlink.create(hyperlinkToken.getText(), params, + SimpleRange.create(hyperlinkToken, lineShift, charShifts)); + } + + private void setHyperlink(BSLDescriptionParser.@Nullable HyperlinkContext hyperlink) { + if (hyperlink != null && hyperlink.link != null) { + this.hyperlinkToken = hyperlink.link; + this.hyperlinkParamsToken = hyperlink.linkParams; + } + } + private void addTypeDescription(BSLDescriptionParser.TypeDescriptionContext typeDescription) { var lastField = lastField(); if (lastField.isPresent()) { @@ -620,7 +652,8 @@ private TypeDescription build(int lineShift, int[] charShifts) { var element = new DescriptionElement(newRange, DescriptionElement.Type.TYPE_NAME); return switch (variant) { - case SIMPLE -> SimpleTypeDescription.create(name, element, description.toString(), fieldList); + case SIMPLE -> SimpleTypeDescription.create(name, element, description.toString(), fieldList, + buildHyperlink(lineShift, charShifts)); case COLLECTION -> CollectionTypeDescription.create( name, element, description.toString(), valueTypes.stream() diff --git a/src/test/java/com/github/_1c_syntax/bsl/parser/description/reader/TypeWithHyperlinkTest.java b/src/test/java/com/github/_1c_syntax/bsl/parser/description/reader/TypeWithHyperlinkTest.java new file mode 100644 index 00000000..19083f8b --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/parser/description/reader/TypeWithHyperlinkTest.java @@ -0,0 +1,107 @@ +/* + * This file is a part of BSL Parser. + * + * Copyright (c) 2018-2026 + * Alexey Sosnoviy , Nikita Fedkin , Sergey Batanov + * + * SPDX-License-Identifier: LGPL-3.0-or-later + * + * BSL Parser 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 Parser 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 Parser. + */ +package com.github._1c_syntax.bsl.parser.description.reader; + +import com.github._1c_syntax.bsl.parser.BSLParser; +import com.github._1c_syntax.bsl.parser.BSLTokenizer; +import com.github._1c_syntax.bsl.parser.description.MethodDescription; +import com.github._1c_syntax.bsl.parser.description.TypeDescription; +import org.antlr.v4.runtime.Token; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Тип, уточнённый ссылкой: {@code СтрокаТабличнойЧасти: См. Справочник.Товары.ЕдиницыИзмерения}. + * Голова говорит, чем значение является, ссылка — откуда взять его состав. + */ +class TypeWithHyperlinkTest { + + private List getTokens(String example) { + var tokenizer = new BSLTokenizer(example); + return tokenizer.getTokens().stream() + .filter(token -> token.getType() == BSLParser.LINE_COMMENT) + .toList(); + } + + @Test + void typeRefinedByHyperlinkKeepsBothParts() { + // given + var src = "// Параметры:\n// Объект - СтрокаТабличнойЧасти: См. Справочник.Товары.ЕдиницыИзмерения\n"; + + // when + var description = MethodDescription.create(getTokens(src)); + + // then + assertThat(description.getParameters()) + .singleElement() + .satisfies(parameter -> { + assertThat(parameter.name()).isEqualTo("Объект"); + assertThat(parameter.types()).singleElement().satisfies(type -> { + assertThat(type.variant()).isEqualTo(TypeDescription.Variant.SIMPLE); + assertThat(type.name()).isEqualTo("СтрокаТабличнойЧасти"); + assertThat(type.hyperlink()).isNotNull(); + assertThat(type.hyperlink().link()).isEqualTo("Справочник.Товары.ЕдиницыИзмерения"); + }); + }); + } + + @Test + void hyperlinkWithoutColonDoesNotRefineType() { + // given: разделителем в этой записи служит двоеточие, без него это не уточнение типа. + var src = "// Параметры:\n// Объект - СтрокаТабличнойЧасти См. Справочник.Товары.ЕдиницыИзмерения\n"; + + // when + var description = MethodDescription.create(getTokens(src)); + + // then: ссылка осталась сама по себе — уточнённого ею простого типа не появилось. + var types = description.getParameters().stream() + .flatMap(parameter -> parameter.types().stream()) + .toList(); + assertThat(types) + .filteredOn(type -> type.variant() == TypeDescription.Variant.HYPERLINK) + .singleElement() + .satisfies(type -> + assertThat(type.hyperlink().link()).isEqualTo("Справочник.Товары.ЕдиницыИзмерения")); + assertThat(types) + .filteredOn(type -> type.variant() == TypeDescription.Variant.SIMPLE) + .allSatisfy(type -> assertThat(type.hyperlink()).isNull()); + } + + @Test + void plainTypeHasNoHyperlink() { + // given + var src = "// Параметры:\n// Объект - СтрокаТабличнойЧасти\n"; + + // when + var description = MethodDescription.create(getTokens(src)); + + // then + assertThat(description.getParameters()) + .singleElement() + .satisfies(parameter -> assertThat(parameter.types()) + .singleElement() + .satisfies(type -> assertThat(type.hyperlink()).isNull())); + } +}