Skip to content

Commit 11a5848

Browse files
authored
Merge pull request #3715 from 1c-syntax/copilot/highlight-namespace-in-modules
feat: Add semantic token highlighting for common module names as namespace
2 parents 278eae9 + 330620d commit 11a5848

File tree

3 files changed

+286
-0
lines changed

3 files changed

+286
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* This file is a part of BSL Language Server.
3+
*
4+
* Copyright (c) 2018-2025
5+
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
6+
*
7+
* SPDX-License-Identifier: LGPL-3.0-or-later
8+
*
9+
* BSL Language Server is free software; you can redistribute it and/or
10+
* modify it under the terms of the GNU Lesser General Public
11+
* License as published by the Free Software Foundation; either
12+
* version 3.0 of the License, or (at your option) any later version.
13+
*
14+
* BSL Language Server is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
* Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public
20+
* License along with BSL Language Server.
21+
*/
22+
package com.github._1c_syntax.bsl.languageserver.semantictokens;
23+
24+
import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;
25+
import com.github._1c_syntax.bsl.languageserver.context.symbol.ModuleSymbol;
26+
import com.github._1c_syntax.bsl.languageserver.context.symbol.Symbol;
27+
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex;
28+
import com.github._1c_syntax.bsl.types.ModuleType;
29+
import lombok.RequiredArgsConstructor;
30+
import org.eclipse.lsp4j.SemanticTokenTypes;
31+
import org.eclipse.lsp4j.SymbolKind;
32+
import org.springframework.stereotype.Component;
33+
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
37+
/**
38+
* Сапплаер семантических токенов для ссылок на модули.
39+
* <p>
40+
* Предоставляет подсветку имён общих модулей как namespace.
41+
*/
42+
@Component
43+
@RequiredArgsConstructor
44+
public class ModuleReferenceSemanticTokensSupplier implements SemanticTokensSupplier {
45+
46+
private final ReferenceIndex referenceIndex;
47+
private final SemanticTokensHelper helper;
48+
49+
@Override
50+
public List<SemanticTokenEntry> getSemanticTokens(DocumentContext documentContext) {
51+
List<SemanticTokenEntry> entries = new ArrayList<>();
52+
var uri = documentContext.getUri();
53+
54+
for (var reference : referenceIndex.getReferencesFrom(uri, SymbolKind.Module)) {
55+
if (!isCommonModuleReference(reference.symbol())) {
56+
continue;
57+
}
58+
helper.addRange(entries, reference.selectionRange(), SemanticTokenTypes.Namespace);
59+
}
60+
61+
return entries;
62+
}
63+
64+
private static boolean isCommonModuleReference(Symbol symbol) {
65+
if (!(symbol instanceof ModuleSymbol moduleSymbol)) {
66+
return false;
67+
}
68+
return moduleSymbol.getOwner().getModuleType() == ModuleType.CommonModule;
69+
}
70+
}

src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
package com.github._1c_syntax.bsl.languageserver.providers;
2323

2424
import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;
25+
import com.github._1c_syntax.bsl.languageserver.context.ServerContext;
2526
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndexFiller;
2627
import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterEachTestMethod;
2728
import com.github._1c_syntax.bsl.languageserver.util.TestUtils;
29+
import com.github._1c_syntax.utils.Absolute;
30+
import org.apache.commons.io.FileUtils;
2831
import org.eclipse.lsp4j.Position;
2932
import org.eclipse.lsp4j.Range;
3033
import org.eclipse.lsp4j.SemanticTokenModifiers;
@@ -39,7 +42,10 @@
3942
import org.springframework.beans.factory.annotation.Autowired;
4043
import org.springframework.boot.test.context.SpringBootTest;
4144

45+
import java.io.File;
46+
import java.io.IOException;
4247
import java.net.URI;
48+
import java.nio.charset.StandardCharsets;
4349
import java.util.ArrayList;
4450
import java.util.List;
4551
import java.util.Set;
@@ -59,6 +65,9 @@ class SemanticTokensProviderTest {
5965
@Autowired
6066
private ReferenceIndexFiller referenceIndexFiller;
6167

68+
@Autowired
69+
private ServerContext serverContext;
70+
6271
// region Helper types and methods
6372

6473
/**
@@ -1661,5 +1670,59 @@ void rangeTokens_withSdblQuery() {
16611670
}
16621671

16631672
// endregion
1673+
1674+
// region Common module namespace tests
1675+
1676+
@Test
1677+
void variableWithCommonModuleNotHighlightedAsNamespace() throws IOException {
1678+
// given - code with variable that holds reference to common module via ОбщегоНазначения.ОбщийМодуль
1679+
// The variable itself should NOT be highlighted as namespace
1680+
// Pattern: Модуль = ОбщегоНазначения.ОбщийМодуль("..."); Модуль.Метод();
1681+
var path = Absolute.path("src/test/resources/metadata/designer");
1682+
serverContext.setConfigurationRoot(path);
1683+
1684+
// Load the common module
1685+
var file = new File("src/test/resources/metadata/designer",
1686+
"CommonModules/ПервыйОбщийМодуль/Ext/Module.bsl");
1687+
var uri = Absolute.uri(file);
1688+
TestUtils.getDocumentContext(
1689+
uri,
1690+
FileUtils.readFileToString(file, StandardCharsets.UTF_8),
1691+
serverContext
1692+
);
1693+
1694+
// Load a document with the pattern
1695+
var documentContext = TestUtils.getDocumentContextFromFile(
1696+
"./src/test/resources/references/ReferenceIndexCommonModuleVariable.bsl"
1697+
);
1698+
referenceIndexFiller.fill(documentContext);
1699+
1700+
// when
1701+
TextDocumentIdentifier textDocumentIdentifier = TestUtils.getTextDocumentIdentifier(documentContext.getUri());
1702+
SemanticTokens tokens = provider.getSemanticTokensFull(documentContext, new SemanticTokensParams(textDocumentIdentifier));
1703+
var decoded = decode(tokens.getData());
1704+
1705+
// then
1706+
int namespaceTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Namespace);
1707+
1708+
// Find all namespace tokens
1709+
var namespaceTokens = decoded.stream()
1710+
.filter(t -> t.type() == namespaceTypeIdx)
1711+
.toList();
1712+
1713+
// Variable "МодульУправлениеДоступом" should NOT be highlighted as namespace
1714+
// It appears on lines 6, 7, 10, 13 (0-indexed) in ReferenceIndexCommonModuleVariable.bsl
1715+
// Lines 7, 10, 13 are where the variable is used for method calls
1716+
for (var token : namespaceTokens) {
1717+
// Namespace tokens should not be at position 4 (start of "МодульУправлениеДоступом") on variable usage lines
1718+
if (token.line() == 7 || token.line() == 10 || token.line() == 13) {
1719+
assertThat(token.start())
1720+
.as("Variable 'МодульУправлениеДоступом' at line %d should not be namespace", token.line())
1721+
.isNotEqualTo(4);
1722+
}
1723+
}
1724+
}
1725+
1726+
// endregion
16641727
}
16651728

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* This file is a part of BSL Language Server.
3+
*
4+
* Copyright (c) 2018-2025
5+
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
6+
*
7+
* SPDX-License-Identifier: LGPL-3.0-or-later
8+
*
9+
* BSL Language Server is free software; you can redistribute it and/or
10+
* modify it under the terms of the GNU Lesser General Public
11+
* License as published by the Free Software Foundation; either
12+
* version 3.0 of the License, or (at your option) any later version.
13+
*
14+
* BSL Language Server is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
* Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public
20+
* License along with BSL Language Server.
21+
*/
22+
package com.github._1c_syntax.bsl.languageserver.semantictokens;
23+
24+
import com.github._1c_syntax.bsl.languageserver.context.ServerContext;
25+
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndexFiller;
26+
import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterEachTestMethod;
27+
import com.github._1c_syntax.bsl.languageserver.util.TestUtils;
28+
import com.github._1c_syntax.utils.Absolute;
29+
import org.apache.commons.io.FileUtils;
30+
import org.eclipse.lsp4j.SemanticTokenTypes;
31+
import org.eclipse.lsp4j.SemanticTokensLegend;
32+
import org.junit.jupiter.api.Test;
33+
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.boot.test.context.SpringBootTest;
35+
36+
import java.io.File;
37+
import java.io.IOException;
38+
import java.nio.charset.StandardCharsets;
39+
40+
import static org.assertj.core.api.Assertions.assertThat;
41+
42+
@SpringBootTest
43+
@CleanupContextBeforeClassAndAfterEachTestMethod
44+
class ModuleReferenceSemanticTokensSupplierTest {
45+
46+
@Autowired
47+
private ModuleReferenceSemanticTokensSupplier supplier;
48+
49+
@Autowired
50+
private SemanticTokensLegend legend;
51+
52+
@Autowired
53+
private ReferenceIndexFiller referenceIndexFiller;
54+
55+
@Autowired
56+
private ServerContext serverContext;
57+
58+
@Test
59+
void testCommonModuleReference() throws IOException {
60+
// given
61+
var path = Absolute.path("src/test/resources/metadata/designer");
62+
serverContext.setConfigurationRoot(path);
63+
64+
// Load the common module
65+
var file = new File("src/test/resources/metadata/designer",
66+
"CommonModules/ПервыйОбщийМодуль/Ext/Module.bsl");
67+
var uri = Absolute.uri(file);
68+
TestUtils.getDocumentContext(
69+
uri,
70+
FileUtils.readFileToString(file, StandardCharsets.UTF_8),
71+
serverContext
72+
);
73+
74+
// Load a document that references the common module
75+
var documentContext = TestUtils.getDocumentContextFromFile(
76+
"./src/test/resources/references/ReferenceIndexCommonModuleVariable.bsl"
77+
);
78+
referenceIndexFiller.fill(documentContext);
79+
80+
// when
81+
var tokens = supplier.getSemanticTokens(documentContext);
82+
83+
// then
84+
int namespaceTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Namespace);
85+
var namespaceTokens = tokens.stream()
86+
.filter(t -> t.type() == namespaceTypeIdx)
87+
.toList();
88+
// Common module name should be highlighted as namespace
89+
assertThat(namespaceTokens).isNotEmpty();
90+
}
91+
92+
@Test
93+
void testNoTokensWithoutCommonModuleReference() {
94+
// given - simple code without common module reference
95+
String bsl = """
96+
Процедура Тест()
97+
Сообщить("Привет");
98+
КонецПроцедуры
99+
""";
100+
101+
var documentContext = TestUtils.getDocumentContext(bsl);
102+
referenceIndexFiller.fill(documentContext);
103+
104+
// when
105+
var tokens = supplier.getSemanticTokens(documentContext);
106+
107+
// then
108+
assertThat(tokens).isEmpty();
109+
}
110+
111+
@Test
112+
void testVariableWithCommonModuleNotHighlightedAsNamespace() throws IOException {
113+
// given - code with variable that holds reference to common module via ОбщегоНазначения.ОбщийМодуль
114+
// The variable itself should NOT be highlighted as namespace
115+
var path = Absolute.path("src/test/resources/metadata/designer");
116+
serverContext.setConfigurationRoot(path);
117+
118+
// Load the common module
119+
var file = new File("src/test/resources/metadata/designer",
120+
"CommonModules/ПервыйОбщийМодуль/Ext/Module.bsl");
121+
var uri = Absolute.uri(file);
122+
TestUtils.getDocumentContext(
123+
uri,
124+
FileUtils.readFileToString(file, StandardCharsets.UTF_8),
125+
serverContext
126+
);
127+
128+
// Load a document with the pattern: Модуль = ОбщегоНазначения.ОбщийМодуль("..."); Модуль.Метод();
129+
var documentContext = TestUtils.getDocumentContextFromFile(
130+
"./src/test/resources/references/ReferenceIndexCommonModuleVariable.bsl"
131+
);
132+
referenceIndexFiller.fill(documentContext);
133+
134+
// when
135+
var tokens = supplier.getSemanticTokens(documentContext);
136+
137+
// then
138+
int namespaceTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Namespace);
139+
var namespaceTokens = tokens.stream()
140+
.filter(t -> t.type() == namespaceTypeIdx)
141+
.toList();
142+
143+
// Variable names like "МодульУправлениеДоступом" should NOT appear as namespace tokens
144+
// Only direct common module names should be namespace tokens
145+
for (var token : namespaceTokens) {
146+
// Check that the token is not on a line where variable is used (lines 7, 8, 10, 13 in the test file)
147+
// Line 7 is where the variable is assigned - "МодульУправлениеДоступом" should not be namespace
148+
// The only namespace token should be on line 7 for expression "ОбщегоНазначения.ОбщийМодуль(...)"
149+
// but that's a method call pattern, not a direct module reference
150+
assertThat(token.line()).as("Namespace token should not be on variable usage lines").isNotIn(7, 10, 13);
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)