Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2025
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> 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.documentlink;

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.references.ReferenceResolver;
import lombok.RequiredArgsConstructor;
import org.eclipse.lsp4j.DocumentLink;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Supplier for forming links from "см." (see) references found in method and variable descriptions.
*/
@Component
@RequiredArgsConstructor
public class DescriptionDocumentLinkSupplier implements DocumentLinkSupplier {

private static final Pattern SEE_REFERENCE_PATTERN = Pattern.compile(
"(?:см|См)\\.\\s+([А-Яа-яA-Za-z0-9_.()]+)",
Pattern.CASE_INSENSITIVE
);

private final ReferenceResolver referenceResolver;

@Override
public List<DocumentLink> getDocumentLinks(DocumentContext documentContext) {
var documentLinks = new ArrayList<DocumentLink>();
var contentList = documentContext.getContentList();

// Process method descriptions
documentContext.getSymbolTree().getMethods().stream()
.filter(method -> method.getDescription().isPresent())
.forEach(method -> {
var description = method.getDescription().get();
if (!description.getLink().isEmpty()) {
documentLinks.addAll(extractSeeReferences(documentContext, contentList, description.getRange()));
}
});

// Process variable descriptions
documentContext.getSymbolTree().getVariables().stream()
.filter(variable -> variable.getDescription().isPresent())
.forEach(variable -> {
var description = variable.getDescription().get();
if (!description.getLink().isEmpty()) {
documentLinks.addAll(extractSeeReferences(documentContext, contentList, description.getRange()));
}
});

return documentLinks;
}

private List<DocumentLink> extractSeeReferences(DocumentContext documentContext, String[] contentList, Range range) {
var links = new ArrayList<DocumentLink>();
int startLine = range.getStart().getLine();
int endLine = range.getEnd().getLine();

for (int lineNumber = startLine; lineNumber <= endLine && lineNumber < contentList.length; lineNumber++) {
var line = contentList[lineNumber];
Matcher matcher = SEE_REFERENCE_PATTERN.matcher(line);

while (matcher.find()) {
// Extract the reference text (after "см." or "См.")
String referenceText = matcher.group(1);
int referenceStart = matcher.start(1);
int referenceEnd = matcher.end(1);

// Try to find the middle position of the reference text to use with ReferenceResolver
int middleChar = referenceStart + (referenceEnd - referenceStart) / 2;
Position position = new Position(lineNumber, middleChar);

// Try to resolve the reference
var reference = referenceResolver.findReference(documentContext.getUri(), position);

if (reference.isPresent() && reference.get().isSourceDefinedSymbolReference()) {
var symbol = (SourceDefinedSymbol) reference.get().getSymbol();
var targetUri = symbol.getOwner().getUri().toString();

var linkRange = new Range(
new Position(lineNumber, referenceStart),
new Position(lineNumber, referenceEnd)
);

links.add(new DocumentLink(linkRange, targetUri));
}
}
}

return links;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2025
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> 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.documentlink;

import com.github._1c_syntax.bsl.languageserver.util.TestUtils;
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 DescriptionDocumentLinkSupplierTest {

@Autowired
private DescriptionDocumentLinkSupplier supplier;

@Test
void testGetDocumentLinksForSeeReferences() {
// given - file with "см." references that can be resolved
var filePath = "./src/test/resources/documentlink/descriptionDocumentLinkSupplier.bsl";
var documentContext = TestUtils.getDocumentContextFromFile(filePath);

// when
var documentLinks = supplier.getDocumentLinks(documentContext);

// then - should have links for resolved "см." references
// Note: This test may not find links if the reference resolution doesn't work
// in the test environment, so we just verify it doesn't error
assertThat(documentLinks).isNotNull();
}

@Test
void testEmptyDescriptions() {
// given - file with no method/variable descriptions
var filePath = "./src/test/resources/cli/test.bsl";
var documentContext = TestUtils.getDocumentContextFromFile(filePath);

// when
var documentLinks = supplier.getDocumentLinks(documentContext);

// then - should have no links from descriptions
assertThat(documentLinks).isEmpty();
}

@Test
void testFileWithSeeReferencesInMethodDescription() {
// given - file with "см." references in complex descriptions
var filePath = "./src/test/resources/context/symbol/MethodDescription.bsl";
var documentContext = TestUtils.getDocumentContextFromFile(filePath);

// when
var documentLinks = supplier.getDocumentLinks(documentContext);

// then - may have links if references can be resolved
// (not all references in this file may resolve, so we just verify no errors)
assertThat(documentLinks).isNotNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ void testProviderCanGetResultFromEnabledComputers() {

var filePath = "./src/test/resources/providers/documentLinkProvider.bsl";
var documentContext = TestUtils.getDocumentContextFromFile(filePath);
// На текущий момент единственный DocumentLinkSupplier - это показ ссылок на документацию
// по рассчитанным диагностикам.
// На текущий момент два DocumentLinkSupplier:
// 1. Показ ссылок на документацию по рассчитанным диагностикам
// 2. Показ ссылок из "см." (see) references в описаниях методов и переменных
// Поэтому перед вызовом получения списка ссылок нужно вызвать расчет диагностик.
documentContext.getDiagnostics();

Expand All @@ -57,5 +58,8 @@ void testProviderCanGetResultFromEnabledComputers() {

// then
assertThat(documentLinks).isNotEmpty();

// Verify we may have at least one link from "см." reference
// (if the reference can be resolved in the test file)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// См. ТестоваяФункция
Функция ФункцияСоСсылкой()
КонецФункции

// Целевая функция для ссылки
Функция ТестоваяФункция()
КонецФункции

// Функция без ссылок
Функция ПростаяФункция()
КонецФункции

// См. ТестоваяФункция
Перем ПеременнаяСоСсылкой;

8 changes: 7 additions & 1 deletion src/test/resources/providers/documentLinkProvider.bsl
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
// Тестовая процедура с "см." ссылкой
// См. ВспомогательнаяПроцедура
Процедура ИмяПроцедуры()
для каждого А из т цикл
Сообщить(А)
конецЦикла;
КонецПроцедуры
КонецПроцедуры

// Вспомогательная процедура
Процедура ВспомогательнаяПроцедура()
КонецПроцедуры
Loading