From 7154dc65ce01e743feb7380f7e60a9456026d58e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 09:59:10 +0000 Subject: [PATCH 01/13] Initial plan From 9e98aa5a4aca06f1ebc49e36ad2c68e24d160ede Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 10:13:52 +0000 Subject: [PATCH 02/13] Add configurable template function highlighting for semantic tokens Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../LanguageServerConfiguration.java | 6 + .../semantictokens/SemanticTokensOptions.java | 77 ++++++++ .../semantictokens/package-info.java | 28 +++ .../StringSemanticTokensSupplier.java | 14 +- .../strings/SpecialContextVisitor.java | 172 ++++++++++++++++-- .../languageserver/configuration/schema.json | 29 +++ .../StringSemanticTokensSupplierTest.java | 102 +++++++++++ 7 files changed, 414 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/package-info.java diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfiguration.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfiguration.java index 12826196162..63844d7226c 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfiguration.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfiguration.java @@ -33,6 +33,7 @@ import com.github._1c_syntax.bsl.languageserver.configuration.formating.FormattingOptions; import com.github._1c_syntax.bsl.languageserver.configuration.inlayhints.InlayHintOptions; import com.github._1c_syntax.bsl.languageserver.configuration.references.ReferencesOptions; +import com.github._1c_syntax.bsl.languageserver.configuration.semantictokens.SemanticTokensOptions; import com.github._1c_syntax.utils.Absolute; import jakarta.annotation.PostConstruct; import lombok.AccessLevel; @@ -102,6 +103,10 @@ public class LanguageServerConfiguration { @Setter(value = AccessLevel.NONE) private ReferencesOptions referencesOptions = new ReferencesOptions(); + @JsonProperty("semanticTokens") + @Setter(value = AccessLevel.NONE) + private SemanticTokensOptions semanticTokensOptions = new SemanticTokensOptions(); + private String siteRoot = "https://1c-syntax.github.io/bsl-language-server"; private boolean useDevSite; @@ -217,5 +222,6 @@ private void copyPropertiesFrom(LanguageServerConfiguration configuration) { PropertyUtils.copyProperties(this.documentLinkOptions, configuration.documentLinkOptions); PropertyUtils.copyProperties(this.formattingOptions, configuration.formattingOptions); PropertyUtils.copyProperties(this.referencesOptions, configuration.referencesOptions); + PropertyUtils.copyProperties(this.semanticTokensOptions, configuration.semanticTokensOptions); } } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java new file mode 100644 index 00000000000..6b77dc70e62 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java @@ -0,0 +1,77 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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.configuration.semantictokens; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Настройки для семантических токенов. + *

+ * Позволяет указать дополнительные функции-шаблонизаторы строк, + * аналогичные СтрШаблон/StrTemplate, для подсветки плейсхолдеров (%1, %2 и т.д.). + */ +@Data +@AllArgsConstructor(onConstructor = @__({@JsonCreator(mode = JsonCreator.Mode.DISABLED)})) +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class SemanticTokensOptions { + + /** + * Список паттернов "Модуль.Метод" для функций-шаблонизаторов строк. + *

+ * Строки внутри вызовов этих функций будут подсвечиваться так же, + * как строки в СтрШаблон/StrTemplate (с выделением плейсхолдеров %1, %2 и т.д.). + *

+ * Формат: "ИмяМодуля.ИмяМетода", например: + *

+ *

+ * По умолчанию включает стандартные варианты из БСП. + */ + private List strTemplateMethods = new ArrayList<>(List.of( + // Локальные вызовы + "ПодставитьПараметрыВСтроку", + "SubstituteParametersToString", + "SubstituteParametersInString", + // Стандартные модули БСП + "СтроковыеФункции.ПодставитьПараметрыВСтроку", + "СтроковыеФункцииКлиент.ПодставитьПараметрыВСтроку", + "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", + // Английские варианты + "StringFunctions.SubstituteParametersToString", + "StringFunctionsClient.SubstituteParametersToString", + "StringFunctionsClientServer.SubstituteParametersToString", + "StringFunctions.SubstituteParametersInString", + "StringFunctionsClient.SubstituteParametersInString", + "StringFunctionsClientServer.SubstituteParametersInString" + )); +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/package-info.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/package-info.java new file mode 100644 index 00000000000..a8aad39ca28 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/package-info.java @@ -0,0 +1,28 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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. + */ +/** + * Пакет содержит настройки для семантических токенов. + */ +@NullMarked +package com.github._1c_syntax.bsl.languageserver.configuration.semantictokens; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java index 69cc5155548..fc1cdeeecf4 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java @@ -21,6 +21,7 @@ */ package com.github._1c_syntax.bsl.languageserver.semantictokens; +import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration; import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; import com.github._1c_syntax.bsl.languageserver.semantictokens.strings.AstTokenInfo; import com.github._1c_syntax.bsl.languageserver.semantictokens.strings.QueryContext; @@ -33,6 +34,7 @@ import com.github._1c_syntax.bsl.languageserver.utils.MultilingualStringAnalyser; import com.github._1c_syntax.bsl.languageserver.utils.Ranges; import com.github._1c_syntax.bsl.parser.BSLLexer; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.antlr.v4.runtime.Token; import org.eclipse.lsp4j.Position; @@ -57,6 +59,7 @@ *

  • Запросы SDBL: разбивает строки на части вокруг токенов запроса и добавляет токены SDBL
  • *
  • НСтр/NStr: подсвечивает языковые ключи (ru=, en=)
  • *
  • СтрШаблон/StrTemplate: подсвечивает плейсхолдеры (%1, %2)
  • + *
  • Конфигурируемые функции-шаблонизаторы: подсвечивает плейсхолдеры (%1, %2)
  • *
  • Обычные строки: выдаёт токен для всей строки
  • * */ @@ -72,6 +75,15 @@ public class StringSemanticTokensSupplier implements SemanticTokensSupplier { ); private final SemanticTokensHelper helper; + private final LanguageServerConfiguration configuration; + + private SpecialContextVisitor.ParsedStrTemplateMethods parsedStrTemplateMethods; + + @PostConstruct + private void init() { + var strTemplateMethods = configuration.getSemanticTokensOptions().getStrTemplateMethods(); + parsedStrTemplateMethods = SpecialContextVisitor.parseStrTemplateMethods(strTemplateMethods); + } @Override public List getSemanticTokens(DocumentContext documentContext) { @@ -277,7 +289,7 @@ private void processSpecialContext( private Map collectSpecialStringContexts(DocumentContext documentContext) { Map contexts = new HashMap<>(); - var visitor = new SpecialContextVisitor(contexts); + var visitor = new SpecialContextVisitor(contexts, parsedStrTemplateMethods); visitor.visit(documentContext.getAst()); return contexts; } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java index de314e65eee..077dd164d60 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java @@ -31,7 +31,10 @@ import org.jspecify.annotations.Nullable; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -44,6 +47,9 @@ *

    * Также поддерживает поиск строк-шаблонов, которые присвоены переменным, * а затем используются в вызове СтрШаблон. + *

    + * Дополнительно поддерживает конфигурируемые функции-шаблонизаторы, + * аналогичные СтрШаблон (например, СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку). */ public class SpecialContextVisitor extends BSLParserBaseVisitor { @@ -55,6 +61,56 @@ public class SpecialContextVisitor extends BSLParserBaseVisitor { ); private final Map contexts; + private final ParsedStrTemplateMethods parsedMethods; + + /** + * Предварительно разобранные паттерны функций-шаблонизаторов. + *

    + * Структура: + * - localMethods: Set методов для локального вызова (без модуля) + * - moduleMethodPairs: Map из имени модуля -> Set методов этого модуля + */ + public record ParsedStrTemplateMethods( + Set localMethods, + Map> moduleMethodPairs + ) { + /** + * Создаёт пустой объект без дополнительных методов. + */ + public static ParsedStrTemplateMethods empty() { + return new ParsedStrTemplateMethods(Set.of(), Map.of()); + } + } + + /** + * Разбирает список паттернов функций-шаблонизаторов один раз. + *

    + * Вызывается один раз при инициализации и результат кэшируется. + * + * @param strTemplateMethods Список паттернов "Модуль.Метод" или "Метод" для локального вызова + * @return Предварительно разобранные паттерны + */ + public static ParsedStrTemplateMethods parseStrTemplateMethods(List strTemplateMethods) { + var localMethods = new HashSet(); + var moduleMethodPairs = new HashMap>(); + + for (var pattern : strTemplateMethods) { + var patternLower = pattern.toLowerCase(Locale.ENGLISH); + + if (patternLower.contains(".")) { + var parts = patternLower.split("\\.", 2); + if (parts.length == 2) { + moduleMethodPairs + .computeIfAbsent(parts[0], k -> new HashSet<>()) + .add(parts[1]); + } + } else { + localMethods.add(patternLower); + } + } + + return new ParsedStrTemplateMethods(localMethods, moduleMethodPairs); + } /** * Создаёт visitor для сбора контекстов строк. @@ -62,7 +118,18 @@ public class SpecialContextVisitor extends BSLParserBaseVisitor { * @param contexts Map для заполнения контекстами строк */ public SpecialContextVisitor(Map contexts) { + this(contexts, ParsedStrTemplateMethods.empty()); + } + + /** + * Создаёт visitor для сбора контекстов строк с конфигурируемыми функциями-шаблонизаторами. + * + * @param contexts Map для заполнения контекстами строк + * @param parsedMethods Предварительно разобранные паттерны функций-шаблонизаторов + */ + public SpecialContextVisitor(Map contexts, ParsedStrTemplateMethods parsedMethods) { this.contexts = contexts; + this.parsedMethods = parsedMethods; } @Override @@ -73,27 +140,106 @@ public Void visitGlobalMethodCall(BSLParser.GlobalMethodCallContext ctx) { context = StringContext.NSTR; } else if (MultilingualStringAnalyser.isStrTemplateCall(ctx)) { context = StringContext.STR_TEMPLATE; + } else if (isConfiguredStrTemplateCall(ctx)) { + context = StringContext.STR_TEMPLATE; } if (context != null) { - var callParams = ctx.doCall().callParamList().callParam(); - if (!callParams.isEmpty()) { - var firstParam = callParams.get(0); - var stringTokens = getStringTokensFromParam(firstParam); + processMethodCallParams(ctx.doCall(), context, ctx); + } - if (stringTokens.isEmpty() && context == StringContext.STR_TEMPLATE) { - // Первый параметр не строковый литерал - возможно, это переменная - // Пытаемся найти присвоение этой переменной - stringTokens = findStringTokensFromVariable(firstParam, ctx); - } + return super.visitGlobalMethodCall(ctx); + } + + @Override + public Void visitCallStatement(BSLParser.CallStatementContext ctx) { + // Обрабатываем вызовы вида Модуль.Метод(...) в отдельных statements (не в выражениях) + if (ctx.IDENTIFIER() != null && ctx.accessCall() != null) { + processModuleMethodCall(ctx.IDENTIFIER().getText(), ctx.accessCall(), ctx); + } - for (Token token : stringTokens) { - contexts.merge(token, context, StringContext::combine); + return super.visitCallStatement(ctx); + } + + @Override + public Void visitComplexIdentifier(BSLParser.ComplexIdentifierContext ctx) { + // Обрабатываем вызовы вида Модуль.Метод(...) в выражениях (присвоениях и т.п.) + var identifier = ctx.IDENTIFIER(); + if (identifier != null && ctx.modifier() != null && !ctx.modifier().isEmpty()) { + for (var modifier : ctx.modifier()) { + var accessCall = modifier.accessCall(); + if (accessCall != null) { + processModuleMethodCall(identifier.getText(), accessCall, ctx); } } } - return super.visitGlobalMethodCall(ctx); + return super.visitComplexIdentifier(ctx); + } + + /** + * Обрабатывает вызов метода модуля вида Модуль.Метод(...). + */ + private void processModuleMethodCall( + String moduleName, + BSLParser.AccessCallContext accessCall, + org.antlr.v4.runtime.ParserRuleContext ctx + ) { + var methodCall = accessCall.methodCall(); + if (methodCall == null || methodCall.methodName() == null) { + return; + } + + var methodName = methodCall.methodName().getText().toLowerCase(Locale.ENGLISH); + var moduleNameLower = moduleName.toLowerCase(Locale.ENGLISH); + + if (isModuleMethodMatch(moduleNameLower, methodName)) { + var doCall = methodCall.doCall(); + if (doCall != null) { + processMethodCallParams(doCall, StringContext.STR_TEMPLATE, ctx); + } + } + } + + /** + * Проверяет, является ли вызов глобального метода конфигурируемым шаблонизатором. + */ + private boolean isConfiguredStrTemplateCall(BSLParser.GlobalMethodCallContext ctx) { + var methodName = ctx.methodName().getText().toLowerCase(Locale.ENGLISH); + return parsedMethods.localMethods().contains(methodName); + } + + /** + * Проверяет, соответствует ли пара "модуль.метод" конфигурируемым паттернам. + */ + private boolean isModuleMethodMatch(String moduleName, String methodName) { + var moduleMethods = parsedMethods.moduleMethodPairs().get(moduleName); + return moduleMethods != null && moduleMethods.contains(methodName); + } + + /** + * Обрабатывает параметры вызова метода. + */ + private void processMethodCallParams( + BSLParser.DoCallContext doCall, + StringContext context, + ParserRuleContext callContext + ) { + var callParams = doCall.callParamList().callParam(); + if (!callParams.isEmpty()) { + var firstParam = callParams.get(0); + var stringTokens = getStringTokensFromParam(firstParam); + + if (stringTokens.isEmpty() && context == StringContext.STR_TEMPLATE) { + // Первый параметр не строковый литерал - возможно, это переменная + // Пытаемся найти присвоение этой переменной + stringTokens = findStringTokensFromVariable(firstParam, callContext); + } + + for (Token token : stringTokens) { + contexts.merge(token, context, StringContext::combine); + } + } } private List getStringTokensFromParam(BSLParser.CallParamContext callParam) { @@ -128,7 +274,7 @@ private List getStringTokensFromParam(BSLParser.CallParamContext callPara */ private List findStringTokensFromVariable( BSLParser.CallParamContext callParam, - BSLParser.GlobalMethodCallContext callContext + ParserRuleContext callContext ) { // Получаем имя переменной из первого параметра var varName = extractVariableName(callParam); diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json index 37bc7f9ce94..6c22dbfbb15 100644 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json @@ -1155,6 +1155,35 @@ ] } } + }, + "semanticTokens": { + "$id": "#/properties/semanticTokens", + "type": "object", + "title": "Semantic tokens configuration.", + "properties": { + "strTemplateMethods": { + "$id": "#/properties/semanticTokens/strTemplateMethods", + "type": "array", + "title": "List of 'Module.Method' patterns for string template functions similar to StrTemplate/СтрШаблон. Strings in these functions will be highlighted with placeholder detection (%1, %2, etc.).", + "items": { + "type": "string" + }, + "default": [ + "ПодставитьПараметрыВСтроку", + "SubstituteParametersToString", + "SubstituteParametersInString", + "СтроковыеФункции.ПодставитьПараметрыВСтроку", + "СтроковыеФункцииКлиент.ПодставитьПараметрыВСтроку", + "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", + "StringFunctions.SubstituteParametersToString", + "StringFunctionsClient.SubstituteParametersToString", + "StringFunctionsClientServer.SubstituteParametersToString", + "StringFunctions.SubstituteParametersInString", + "StringFunctionsClient.SubstituteParametersInString", + "StringFunctionsClientServer.SubstituteParametersInString" + ] + } + } } } } \ No newline at end of file diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java index 247caa77e39..c094c655bac 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java @@ -556,5 +556,107 @@ void testQueryWithAggregateFunction() { // Количество assertThat(functionTokens).hasSize(1); } + + // ==================== Configurable Template Function Tests ==================== + + @Test + void testSubstituteParametersToStringPlaceholders() { + // given - СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку like СтрШаблон + String bsl = """ + Процедура Тест() + Текст = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var tokens = tokens(bsl); + + // then + var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); + // %1, %2 + assertThat(parameterTokens).hasSize(2); + + // Check that string parts are also present + var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); + assertThat(stringTokens).isNotEmpty(); + } + + @Test + void testSubstituteParametersToStringEnglish() { + // given - English variant of the function + String bsl = """ + Процедура Тест() + Text = StringFunctionsClientServer.SubstituteParametersToString("Name: %1, version: %2", Name, Version); + КонецПроцедуры + """; + + // when + var tokens = tokens(bsl); + + // then + var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); + // %1, %2 + assertThat(parameterTokens).hasSize(2); + } + + @Test + void testSubstituteParametersToStringLocal() { + // given - Local call without module prefix (configured in defaults) + String bsl = """ + Процедура Тест() + Текст = ПодставитьПараметрыВСтроку("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var tokens = tokens(bsl); + + // then + var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); + // %1, %2 + assertThat(parameterTokens).hasSize(2); + } + + @Test + void testSubstituteParametersToStringWithVariable() { + // given - template stored in variable, then used in module function call + String bsl = """ + Процедура Тест() + Шаблон = "%1 + %2 = %3"; + Текст = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(Шаблон, А, Б, В); + КонецПроцедуры + """; + + // when + var tokens = tokens(bsl); + + // then - placeholders in the assigned string should be highlighted + var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); + // %1, %2, %3 + assertThat(parameterTokens).hasSize(3); + } + + @Test + void testNStrWithSubstituteParametersToString() { + // given - НСтр combined with ПодставитьПараметрыВСтроку + String bsl = """ + Процедура Тест() + Шаблон = НСтр("ru = 'Привет %1'"); + Текст = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(Шаблон, Имя); + КонецПроцедуры + """; + + // when + var tokens = tokens(bsl); + + // then - should have both language keys (ru) and placeholders (%1) + var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); + // ru + assertThat(propertyTokens).hasSize(1); + + var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); + // %1 + assertThat(parameterTokens).hasSize(1); + } } From 0c6881a35a7c84117fcd1fbfd863513ba9f50aa7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 10:24:53 +0000 Subject: [PATCH 03/13] Address code review: add null safety checks Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../semantictokens/StringSemanticTokensSupplier.java | 5 ++++- .../semantictokens/strings/SpecialContextVisitor.java | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java index fc1cdeeecf4..1c43dcff515 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java @@ -81,7 +81,10 @@ public class StringSemanticTokensSupplier implements SemanticTokensSupplier { @PostConstruct private void init() { - var strTemplateMethods = configuration.getSemanticTokensOptions().getStrTemplateMethods(); + var semanticTokensOptions = configuration.getSemanticTokensOptions(); + var strTemplateMethods = semanticTokensOptions != null + ? semanticTokensOptions.getStrTemplateMethods() + : null; parsedStrTemplateMethods = SpecialContextVisitor.parseStrTemplateMethods(strTemplateMethods); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java index 077dd164d60..dec50d91097 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java @@ -94,12 +94,19 @@ public static ParsedStrTemplateMethods parseStrTemplateMethods(List strT var localMethods = new HashSet(); var moduleMethodPairs = new HashMap>(); + if (strTemplateMethods == null) { + return new ParsedStrTemplateMethods(localMethods, moduleMethodPairs); + } + for (var pattern : strTemplateMethods) { + if (pattern == null || pattern.isBlank()) { + continue; + } var patternLower = pattern.toLowerCase(Locale.ENGLISH); if (patternLower.contains(".")) { var parts = patternLower.split("\\.", 2); - if (parts.length == 2) { + if (parts.length == 2 && !parts[0].isEmpty() && !parts[1].isEmpty()) { moduleMethodPairs .computeIfAbsent(parts[0], k -> new HashSet<>()) .add(parts[1]); From b564116c84f937ada34b2b564d6ff00b26621efe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:35:37 +0000 Subject: [PATCH 04/13] Remove non-documented methods from strTemplateMethods defaults Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../semantictokens/SemanticTokensOptions.java | 17 +++-------------- .../languageserver/configuration/schema.json | 12 +----------- .../StringSemanticTokensSupplierTest.java | 18 ------------------ 3 files changed, 4 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java index 6b77dc70e62..e492221e3e8 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java @@ -58,20 +58,9 @@ public class SemanticTokensOptions { * По умолчанию включает стандартные варианты из БСП. */ private List strTemplateMethods = new ArrayList<>(List.of( - // Локальные вызовы - "ПодставитьПараметрыВСтроку", - "SubstituteParametersToString", - "SubstituteParametersInString", - // Стандартные модули БСП - "СтроковыеФункции.ПодставитьПараметрыВСтроку", - "СтроковыеФункцииКлиент.ПодставитьПараметрыВСтроку", + // Стандартный модуль БСП "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", - // Английские варианты - "StringFunctions.SubstituteParametersToString", - "StringFunctionsClient.SubstituteParametersToString", - "StringFunctionsClientServer.SubstituteParametersToString", - "StringFunctions.SubstituteParametersInString", - "StringFunctionsClient.SubstituteParametersInString", - "StringFunctionsClientServer.SubstituteParametersInString" + // Английский вариант + "StringFunctionsClientServer.SubstituteParametersToString" )); } diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json index 6c22dbfbb15..5a8529d46b6 100644 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json @@ -1169,18 +1169,8 @@ "type": "string" }, "default": [ - "ПодставитьПараметрыВСтроку", - "SubstituteParametersToString", - "SubstituteParametersInString", - "СтроковыеФункции.ПодставитьПараметрыВСтроку", - "СтроковыеФункцииКлиент.ПодставитьПараметрыВСтроку", "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", - "StringFunctions.SubstituteParametersToString", - "StringFunctionsClient.SubstituteParametersToString", - "StringFunctionsClientServer.SubstituteParametersToString", - "StringFunctions.SubstituteParametersInString", - "StringFunctionsClient.SubstituteParametersInString", - "StringFunctionsClientServer.SubstituteParametersInString" + "StringFunctionsClientServer.SubstituteParametersToString" ] } } diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java index c094c655bac..0da105927ee 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java @@ -599,24 +599,6 @@ void testSubstituteParametersToStringEnglish() { assertThat(parameterTokens).hasSize(2); } - @Test - void testSubstituteParametersToStringLocal() { - // given - Local call without module prefix (configured in defaults) - String bsl = """ - Процедура Тест() - Текст = ПодставитьПараметрыВСтроку("Наименование: %1, версия: %2", Наименование, Версия); - КонецПроцедуры - """; - - // when - var tokens = tokens(bsl); - - // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 - assertThat(parameterTokens).hasSize(2); - } - @Test void testSubstituteParametersToStringWithVariable() { // given - template stored in variable, then used in module function call From 2d52ed80200875e37978990495422601abd628c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 06:11:20 +0000 Subject: [PATCH 05/13] Add local call methods to strTemplateMethods defaults Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../semantictokens/SemanticTokensOptions.java | 3 +++ .../languageserver/configuration/schema.json | 2 ++ .../StringSemanticTokensSupplierTest.java | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java index e492221e3e8..f977c9a6255 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java @@ -58,6 +58,9 @@ public class SemanticTokensOptions { * По умолчанию включает стандартные варианты из БСП. */ private List strTemplateMethods = new ArrayList<>(List.of( + // Локальный вызов + "ПодставитьПараметрыВСтроку", + "SubstituteParametersToString", // Стандартный модуль БСП "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", // Английский вариант diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json index 5a8529d46b6..b941252210f 100644 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json @@ -1169,6 +1169,8 @@ "type": "string" }, "default": [ + "ПодставитьПараметрыВСтроку", + "SubstituteParametersToString", "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", "StringFunctionsClientServer.SubstituteParametersToString" ] diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java index 0da105927ee..c094c655bac 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java @@ -599,6 +599,24 @@ void testSubstituteParametersToStringEnglish() { assertThat(parameterTokens).hasSize(2); } + @Test + void testSubstituteParametersToStringLocal() { + // given - Local call without module prefix (configured in defaults) + String bsl = """ + Процедура Тест() + Текст = ПодставитьПараметрыВСтроку("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var tokens = tokens(bsl); + + // then + var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); + // %1, %2 + assertThat(parameterTokens).hasSize(2); + } + @Test void testSubstituteParametersToStringWithVariable() { // given - template stored in variable, then used in module function call From e2dac8b13ed204677bfc6beb0e34886848c8e26e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 06:17:24 +0000 Subject: [PATCH 06/13] Add event listener for configuration changes in StringSemanticTokensSupplier Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../StringSemanticTokensSupplier.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java index 1c43dcff515..4470967a8db 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java @@ -22,6 +22,7 @@ package com.github._1c_syntax.bsl.languageserver.semantictokens; import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration; +import com.github._1c_syntax.bsl.languageserver.configuration.events.LanguageServerConfigurationChangedEvent; import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; import com.github._1c_syntax.bsl.languageserver.semantictokens.strings.AstTokenInfo; import com.github._1c_syntax.bsl.languageserver.semantictokens.strings.QueryContext; @@ -40,6 +41,7 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SemanticTokenTypes; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -81,6 +83,22 @@ public class StringSemanticTokensSupplier implements SemanticTokensSupplier { @PostConstruct private void init() { + updateParsedStrTemplateMethods(); + } + + /** + * Обработчик события {@link LanguageServerConfigurationChangedEvent}. + *

    + * Обновляет кэшированные паттерны функций-шаблонизаторов при изменении конфигурации. + * + * @param event Событие + */ + @EventListener + public void handleEvent(LanguageServerConfigurationChangedEvent event) { + updateParsedStrTemplateMethods(); + } + + private void updateParsedStrTemplateMethods() { var semanticTokensOptions = configuration.getSemanticTokensOptions(); var strTemplateMethods = semanticTokensOptions != null ? semanticTokensOptions.getStrTemplateMethods() From 359b71418d12e30ef851d787ffe656e324644a9d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 06:27:02 +0000 Subject: [PATCH 07/13] Move ParsedStrTemplateMethods to separate class and clean up null checks Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../ParsedStrTemplateMethods.java | 43 ++++++++++++ .../semantictokens/SemanticTokensOptions.java | 35 ++++++++++ .../StringSemanticTokensSupplier.java | 9 +-- .../strings/SpecialContextVisitor.java | 68 +------------------ 4 files changed, 82 insertions(+), 73 deletions(-) create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/ParsedStrTemplateMethods.java diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/ParsedStrTemplateMethods.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/ParsedStrTemplateMethods.java new file mode 100644 index 00000000000..f2fc2f4ab82 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/ParsedStrTemplateMethods.java @@ -0,0 +1,43 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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.configuration.semantictokens; + +import java.util.Map; +import java.util.Set; + +/** + * Предварительно разобранные паттерны функций-шаблонизаторов. + *

    + * Структура: + *

      + *
    • localMethods: Set методов для локального вызова (без модуля)
    • + *
    • moduleMethodPairs: Map из имени модуля -> Set методов этого модуля
    • + *
    + * + * @param localMethods Методы для локального вызова (без указания модуля) + * @param moduleMethodPairs Методы с указанием модуля (модуль -> набор методов) + */ +public record ParsedStrTemplateMethods( + Set localMethods, + Map> moduleMethodPairs +) { +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java index f977c9a6255..8b3c33b9d6e 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java @@ -22,13 +22,17 @@ package com.github._1c_syntax.bsl.languageserver.configuration.semantictokens; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Locale; /** * Настройки для семантических токенов. @@ -66,4 +70,35 @@ public class SemanticTokensOptions { // Английский вариант "StringFunctionsClientServer.SubstituteParametersToString" )); + + /** + * Возвращает предварительно разобранные паттерны функций-шаблонизаторов. + * + * @return Разобранные паттерны для быстрого поиска + */ + @JsonIgnore + public ParsedStrTemplateMethods getParsedStrTemplateMethods() { + var localMethods = new HashSet(); + var moduleMethodPairs = new HashMap>(); + + for (var pattern : strTemplateMethods) { + if (pattern.isBlank()) { + continue; + } + var patternLower = pattern.toLowerCase(Locale.ENGLISH); + + if (patternLower.contains(".")) { + var parts = patternLower.split("\\.", 2); + if (parts.length == 2 && !parts[0].isEmpty() && !parts[1].isEmpty()) { + moduleMethodPairs + .computeIfAbsent(parts[0], k -> new HashSet<>()) + .add(parts[1]); + } + } else { + localMethods.add(patternLower); + } + } + + return new ParsedStrTemplateMethods(localMethods, moduleMethodPairs); + } } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java index 4470967a8db..6f9da5706df 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java @@ -23,6 +23,7 @@ import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration; import com.github._1c_syntax.bsl.languageserver.configuration.events.LanguageServerConfigurationChangedEvent; +import com.github._1c_syntax.bsl.languageserver.configuration.semantictokens.ParsedStrTemplateMethods; import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; import com.github._1c_syntax.bsl.languageserver.semantictokens.strings.AstTokenInfo; import com.github._1c_syntax.bsl.languageserver.semantictokens.strings.QueryContext; @@ -79,7 +80,7 @@ public class StringSemanticTokensSupplier implements SemanticTokensSupplier { private final SemanticTokensHelper helper; private final LanguageServerConfiguration configuration; - private SpecialContextVisitor.ParsedStrTemplateMethods parsedStrTemplateMethods; + private ParsedStrTemplateMethods parsedStrTemplateMethods; @PostConstruct private void init() { @@ -99,11 +100,7 @@ public void handleEvent(LanguageServerConfigurationChangedEvent event) { } private void updateParsedStrTemplateMethods() { - var semanticTokensOptions = configuration.getSemanticTokensOptions(); - var strTemplateMethods = semanticTokensOptions != null - ? semanticTokensOptions.getStrTemplateMethods() - : null; - parsedStrTemplateMethods = SpecialContextVisitor.parseStrTemplateMethods(strTemplateMethods); + parsedStrTemplateMethods = configuration.getSemanticTokensOptions().getParsedStrTemplateMethods(); } @Override diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java index dec50d91097..9f88edb99e0 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/strings/SpecialContextVisitor.java @@ -21,6 +21,7 @@ */ package com.github._1c_syntax.bsl.languageserver.semantictokens.strings; +import com.github._1c_syntax.bsl.languageserver.configuration.semantictokens.ParsedStrTemplateMethods; import com.github._1c_syntax.bsl.languageserver.utils.MultilingualStringAnalyser; import com.github._1c_syntax.bsl.languageserver.utils.Trees; import com.github._1c_syntax.bsl.parser.BSLLexer; @@ -31,8 +32,6 @@ import org.jspecify.annotations.Nullable; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -63,71 +62,6 @@ public class SpecialContextVisitor extends BSLParserBaseVisitor { private final Map contexts; private final ParsedStrTemplateMethods parsedMethods; - /** - * Предварительно разобранные паттерны функций-шаблонизаторов. - *

    - * Структура: - * - localMethods: Set методов для локального вызова (без модуля) - * - moduleMethodPairs: Map из имени модуля -> Set методов этого модуля - */ - public record ParsedStrTemplateMethods( - Set localMethods, - Map> moduleMethodPairs - ) { - /** - * Создаёт пустой объект без дополнительных методов. - */ - public static ParsedStrTemplateMethods empty() { - return new ParsedStrTemplateMethods(Set.of(), Map.of()); - } - } - - /** - * Разбирает список паттернов функций-шаблонизаторов один раз. - *

    - * Вызывается один раз при инициализации и результат кэшируется. - * - * @param strTemplateMethods Список паттернов "Модуль.Метод" или "Метод" для локального вызова - * @return Предварительно разобранные паттерны - */ - public static ParsedStrTemplateMethods parseStrTemplateMethods(List strTemplateMethods) { - var localMethods = new HashSet(); - var moduleMethodPairs = new HashMap>(); - - if (strTemplateMethods == null) { - return new ParsedStrTemplateMethods(localMethods, moduleMethodPairs); - } - - for (var pattern : strTemplateMethods) { - if (pattern == null || pattern.isBlank()) { - continue; - } - var patternLower = pattern.toLowerCase(Locale.ENGLISH); - - if (patternLower.contains(".")) { - var parts = patternLower.split("\\.", 2); - if (parts.length == 2 && !parts[0].isEmpty() && !parts[1].isEmpty()) { - moduleMethodPairs - .computeIfAbsent(parts[0], k -> new HashSet<>()) - .add(parts[1]); - } - } else { - localMethods.add(patternLower); - } - } - - return new ParsedStrTemplateMethods(localMethods, moduleMethodPairs); - } - - /** - * Создаёт visitor для сбора контекстов строк. - * - * @param contexts Map для заполнения контекстами строк - */ - public SpecialContextVisitor(Map contexts) { - this(contexts, ParsedStrTemplateMethods.empty()); - } - /** * Создаёт visitor для сбора контекстов строк с конфигурируемыми функциями-шаблонизаторами. * From d81c81720163abb396f70c8bd9dc15355e8f9924 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 08:25:45 +0000 Subject: [PATCH 08/13] Refactor StringSemanticTokensSupplierTest to use SemanticTokensTestHelper Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../StringSemanticTokensSupplierTest.java | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java index c094c655bac..d5544a38bf6 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java @@ -22,12 +22,14 @@ package com.github._1c_syntax.bsl.languageserver.semantictokens; import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterEachTestMethod; -import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import com.github._1c_syntax.bsl.languageserver.util.SemanticTokensTestHelper; +import com.github._1c_syntax.bsl.languageserver.util.SemanticTokensTestHelper.DecodedToken; import org.eclipse.lsp4j.SemanticTokenTypes; import org.eclipse.lsp4j.SemanticTokensLegend; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; import java.util.List; @@ -35,24 +37,27 @@ @SpringBootTest @CleanupContextBeforeClassAndAfterEachTestMethod +@Import(SemanticTokensTestHelper.class) class StringSemanticTokensSupplierTest { @Autowired private StringSemanticTokensSupplier supplier; + @Autowired + private SemanticTokensTestHelper helper; + @Autowired private SemanticTokensLegend legend; - private List tokens(String bsl) { - var documentContext = TestUtils.getDocumentContext(bsl); - return supplier.getSemanticTokens(documentContext); + private List getTokens(String bsl) { + return helper.getDecodedTokens(bsl, supplier); } private int typeIndex(String semanticTokenType) { return legend.getTokenTypes().indexOf(semanticTokenType); } - private List tokensOfType(List tokens, String semanticTokenType) { + private List tokensOfType(List tokens, String semanticTokenType) { int typeIdx = typeIndex(semanticTokenType); return tokens.stream() .filter(t -> t.type() == typeIdx) @@ -71,7 +76,7 @@ void testSimpleString() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); @@ -90,7 +95,7 @@ void testMultilineString() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); @@ -110,7 +115,7 @@ void testNStrLanguageKeys() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); @@ -132,7 +137,7 @@ void testNStrEnglishName() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); @@ -152,7 +157,7 @@ void testStrTemplatePlaceholders() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -174,7 +179,7 @@ void testStrTemplateEnglishName() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -192,7 +197,7 @@ void testStrTemplatePlaceholdersWithParentheses() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -211,7 +216,7 @@ void testStrTemplateWithVariable() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then - плейсхолдеры в строке-присвоении должны подсвечиваться var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -230,7 +235,7 @@ void testStrTemplateWithVariableAndParentheses() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -250,7 +255,7 @@ void testNStrInsideStrTemplate() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then - должны быть и языковые ключи (ru), и плейсхолдеры (%1) var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); @@ -272,7 +277,7 @@ void testNStrInsideStrTemplateMultiple() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); @@ -295,7 +300,7 @@ void testNStrVariableInStrTemplate() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then - должны быть и языковые ключи (ru), и плейсхолдеры (%1) var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); @@ -318,7 +323,7 @@ void testNStrVariableInStrTemplateMultiple() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); @@ -343,7 +348,7 @@ void testQueryStringSplit() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then // String parts should be split around query tokens @@ -366,7 +371,7 @@ void testMultilineQueryString() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); @@ -388,7 +393,7 @@ void testNStrAndQueryInSameMethod() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); @@ -410,7 +415,7 @@ void testRegularStringNotAffectedByOtherContexts() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); @@ -437,7 +442,7 @@ void testSimpleSelect() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var keywordTokens = tokensOfType(tokens, SemanticTokenTypes.Keyword); @@ -462,7 +467,7 @@ void testQueryWithParameter() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -482,7 +487,7 @@ void testQueryWithVirtualTable() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var methodTokens = tokensOfType(tokens, SemanticTokenTypes.Method); @@ -501,7 +506,7 @@ void testQueryWithValueFunction() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var namespaceTokens = tokensOfType(tokens, SemanticTokenTypes.Namespace); @@ -526,7 +531,7 @@ void testQueryWithEnumValue() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var enumTokens = tokensOfType(tokens, SemanticTokenTypes.Enum); @@ -548,7 +553,7 @@ void testQueryWithAggregateFunction() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var functionTokens = tokensOfType(tokens, SemanticTokenTypes.Function); @@ -569,7 +574,7 @@ void testSubstituteParametersToStringPlaceholders() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -591,7 +596,7 @@ void testSubstituteParametersToStringEnglish() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -609,7 +614,7 @@ void testSubstituteParametersToStringLocal() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -628,7 +633,7 @@ void testSubstituteParametersToStringWithVariable() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then - placeholders in the assigned string should be highlighted var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); @@ -647,7 +652,7 @@ void testNStrWithSubstituteParametersToString() { """; // when - var tokens = tokens(bsl); + var tokens = getTokens(bsl); // then - should have both language keys (ru) and placeholders (%1) var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); @@ -658,5 +663,4 @@ void testNStrWithSubstituteParametersToString() { // %1 assertThat(parameterTokens).hasSize(1); } -} - +} \ No newline at end of file From f20814ee0d2794ae722a9e5eaed69cfa69675a4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 08:38:21 +0000 Subject: [PATCH 09/13] Fix imports in SemanticTokensOptions Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../configuration/semantictokens/SemanticTokensOptions.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java index 8b3c33b9d6e..009178f4224 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java @@ -33,6 +33,8 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Set; /** * Настройки для семантических токенов. @@ -79,7 +81,7 @@ public class SemanticTokensOptions { @JsonIgnore public ParsedStrTemplateMethods getParsedStrTemplateMethods() { var localMethods = new HashSet(); - var moduleMethodPairs = new HashMap>(); + var moduleMethodPairs = new HashMap>(); for (var pattern : strTemplateMethods) { if (pattern.isBlank()) { From c003a246ad878a98f809db33ea2ca4f936da38d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 08:59:10 +0000 Subject: [PATCH 10/13] Rewrite all tests to use ExpectedToken pattern Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../StringSemanticTokensSupplierTest.java | 383 ++++++++---------- 1 file changed, 171 insertions(+), 212 deletions(-) diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java index d5544a38bf6..3343903de2b 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java @@ -23,9 +23,9 @@ import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterEachTestMethod; import com.github._1c_syntax.bsl.languageserver.util.SemanticTokensTestHelper; -import com.github._1c_syntax.bsl.languageserver.util.SemanticTokensTestHelper.DecodedToken; +import com.github._1c_syntax.bsl.languageserver.util.SemanticTokensTestHelper.ExpectedToken; +import org.eclipse.lsp4j.SemanticTokenModifiers; import org.eclipse.lsp4j.SemanticTokenTypes; -import org.eclipse.lsp4j.SemanticTokensLegend; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -46,24 +46,6 @@ class StringSemanticTokensSupplierTest { @Autowired private SemanticTokensTestHelper helper; - @Autowired - private SemanticTokensLegend legend; - - private List getTokens(String bsl) { - return helper.getDecodedTokens(bsl, supplier); - } - - private int typeIndex(String semanticTokenType) { - return legend.getTokenTypes().indexOf(semanticTokenType); - } - - private List tokensOfType(List tokens, String semanticTokenType) { - int typeIdx = typeIndex(semanticTokenType); - return tokens.stream() - .filter(t -> t.type() == typeIdx) - .toList(); - } - // ==================== Regular String Tests ==================== @Test @@ -76,11 +58,13 @@ void testSimpleString() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); - // then - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - assertThat(stringTokens).hasSize(1); + // then - one string token + assertThat(decoded).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 10, 12, SemanticTokenTypes.String, "\"Привет мир\"") + )); } @Test @@ -95,12 +79,15 @@ void testMultilineString() { """; // when - var tokens = getTokens(bsl); - - // then - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - // STRINGSTART, 2x STRINGPART, or STRINGTAIL - assertThat(stringTokens).hasSizeGreaterThanOrEqualTo(3); + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then - multiple string parts + assertThat(decoded).hasSize(3); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 10, 14, SemanticTokenTypes.String, "\"Первая строка"), + new ExpectedToken(2, 2, 14, SemanticTokenTypes.String, "|Вторая строка"), + new ExpectedToken(3, 2, 15, SemanticTokenTypes.String, "|Третья строка\"") + )); } // ==================== NStr Tests ==================== @@ -115,16 +102,13 @@ void testNStrLanguageKeys() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); - // then - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - // ru, en - assertThat(propertyTokens).hasSize(2); - - // Check that string parts are also present - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - assertThat(stringTokens).isNotEmpty(); + // then - language keys (ru, en) are highlighted as Property + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 16, 2, SemanticTokenTypes.Property, "ru"), + new ExpectedToken(1, 29, 2, SemanticTokenTypes.Property, "en") + )); } @Test @@ -137,12 +121,13 @@ void testNStrEnglishName() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); - // then - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - // ru, en - assertThat(propertyTokens).hasSize(2); + // then - NStr works same as НСтр + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 16, 2, SemanticTokenTypes.Property, "ru"), + new ExpectedToken(1, 29, 2, SemanticTokenTypes.Property, "en") + )); } // ==================== StrTemplate Tests ==================== @@ -157,16 +142,13 @@ void testStrTemplatePlaceholders() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); - // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 - assertThat(parameterTokens).hasSize(2); - - // Check that string parts are also present - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - assertThat(stringTokens).isNotEmpty(); + // then - placeholders %1, %2 are highlighted as Parameter + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 35, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 47, 2, SemanticTokenTypes.Parameter, "%2") + )); } @Test @@ -179,12 +161,13 @@ void testStrTemplateEnglishName() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 - assertThat(parameterTokens).hasSize(2); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 29, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 42, 2, SemanticTokenTypes.Parameter, "%2") + )); } @Test @@ -197,12 +180,13 @@ void testStrTemplatePlaceholdersWithParentheses() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); - // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %(1), %(2) - assertThat(parameterTokens).hasSize(2); + // then - %(1) and %(2) syntax + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 21, 4, SemanticTokenTypes.Parameter, "%(1)"), + new ExpectedToken(1, 25, 4, SemanticTokenTypes.Parameter, "%(2)") + )); } @Test @@ -216,12 +200,13 @@ void testStrTemplateWithVariable() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - плейсхолдеры в строке-присвоении должны подсвечиваться - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 из строки НовыйШаблон = "%1 %2" - assertThat(parameterTokens).hasSize(2); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 17, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 20, 2, SemanticTokenTypes.Parameter, "%2") + )); } @Test @@ -235,12 +220,13 @@ void testStrTemplateWithVariableAndParentheses() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %(1), %(2) - assertThat(parameterTokens).hasSize(2); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 12, 4, SemanticTokenTypes.Parameter, "%(1)"), + new ExpectedToken(1, 16, 4, SemanticTokenTypes.Parameter, "%(2)") + )); } // ==================== Combined NStr + StrTemplate Tests ==================== @@ -255,16 +241,13 @@ void testNStrInsideStrTemplate() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - должны быть и языковые ключи (ru), и плейсхолдеры (%1) - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - // ru - assertThat(propertyTokens).hasSize(1); - - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1 - assertThat(parameterTokens).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 27, 2, SemanticTokenTypes.Property, "ru"), + new ExpectedToken(1, 42, 2, SemanticTokenTypes.Parameter, "%1") + )); } @Test @@ -277,16 +260,15 @@ void testNStrInsideStrTemplateMultiple() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - // ru, en - assertThat(propertyTokens).hasSize(2); - - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 - assertThat(parameterTokens).hasSize(2); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 30, 2, SemanticTokenTypes.Property, "ru"), + new ExpectedToken(1, 43, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 48, 2, SemanticTokenTypes.Property, "en"), + new ExpectedToken(1, 60, 2, SemanticTokenTypes.Parameter, "%2") + )); } @Test @@ -300,16 +282,13 @@ void testNStrVariableInStrTemplate() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - должны быть и языковые ключи (ru), и плейсхолдеры (%1) - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - // ru - assertThat(propertyTokens).hasSize(1); - - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1 - assertThat(parameterTokens).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 17, 2, SemanticTokenTypes.Property, "ru"), + new ExpectedToken(1, 32, 2, SemanticTokenTypes.Parameter, "%1") + )); } @Test @@ -323,17 +302,17 @@ void testNStrVariableInStrTemplateMultiple() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - // ru, en - assertThat(propertyTokens).hasSize(2); - - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 (по одному разу в каждой подстроке, но токен один - значит 4 плейсхолдера) - // Нет, здесь один строковый токен, внутри которого 4 вхождения %N - assertThat(parameterTokens).hasSize(4); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 17, 2, SemanticTokenTypes.Property, "ru"), + new ExpectedToken(1, 30, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 35, 2, SemanticTokenTypes.Parameter, "%2"), + new ExpectedToken(1, 40, 2, SemanticTokenTypes.Property, "en"), + new ExpectedToken(1, 52, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 59, 2, SemanticTokenTypes.Parameter, "%2") + )); } // ==================== Query String Tests ==================== @@ -348,14 +327,15 @@ void testQueryStringSplit() { """; // when - var tokens = getTokens(bsl); - - // then - // String parts should be split around query tokens - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - - // Should have multiple string parts (quotes and spaces around keywords) - assertThat(stringTokens).hasSizeGreaterThan(1); + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then - query keywords are highlighted + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 18, 7, SemanticTokenTypes.Keyword, "ВЫБРАТЬ"), + new ExpectedToken(1, 33, 2, SemanticTokenTypes.Keyword, "ИЗ"), + new ExpectedToken(1, 36, 10, SemanticTokenTypes.Namespace, "Справочник"), + new ExpectedToken(1, 47, 12, SemanticTokenTypes.Class, "Номенклатура") + )); } @Test @@ -371,13 +351,15 @@ void testMultilineQueryString() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - - // Should have string parts on each line - assertThat(stringTokens).isNotEmpty(); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 18, 7, SemanticTokenTypes.Keyword, "ВЫБРАТЬ"), + new ExpectedToken(3, 3, 2, SemanticTokenTypes.Keyword, "ИЗ"), + new ExpectedToken(4, 5, 10, SemanticTokenTypes.Namespace, "Справочник"), + new ExpectedToken(4, 16, 12, SemanticTokenTypes.Class, "Номенклатура") + )); } // ==================== Mixed Context Tests ==================== @@ -393,16 +375,16 @@ void testNStrAndQueryInSameMethod() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - // ru from NStr - assertThat(propertyTokens).hasSize(1); - - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - // Should have string parts from both NStr and query - assertThat(stringTokens).hasSizeGreaterThan(2); + helper.assertContainsTokens(decoded, List.of( + // NStr tokens + new ExpectedToken(1, 20, 2, SemanticTokenTypes.Property, "ru"), + // Query tokens + new ExpectedToken(2, 18, 7, SemanticTokenTypes.Keyword, "ВЫБРАТЬ"), + new ExpectedToken(2, 36, 10, SemanticTokenTypes.Namespace, "Справочник") + )); } @Test @@ -415,19 +397,13 @@ void testRegularStringNotAffectedByOtherContexts() { """; // when - var tokens = getTokens(bsl); - - // then - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - // Single string token for the whole string - assertThat(stringTokens).hasSize(1); + var decoded = helper.getDecodedTokens(bsl, supplier); - // No Property or Parameter tokens - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - assertThat(propertyTokens).isEmpty(); - - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - assertThat(parameterTokens).isEmpty(); + // then - just one string token + assertThat(decoded).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 18, 38, SemanticTokenTypes.String, "\"Это просто строка без НСтр и запроса\"") + )); } // ==================== SDBL Query Token Tests ==================== @@ -442,19 +418,15 @@ void testSimpleSelect() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var keywordTokens = tokensOfType(tokens, SemanticTokenTypes.Keyword); - var namespaceTokens = tokensOfType(tokens, SemanticTokenTypes.Namespace); - var classTokens = tokensOfType(tokens, SemanticTokenTypes.Class); - - // Выбрать, из - assertThat(keywordTokens).hasSizeGreaterThanOrEqualTo(2); - // Справочник - assertThat(namespaceTokens).hasSize(1); - // Контрагенты - assertThat(classTokens).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 12, 7, SemanticTokenTypes.Keyword, "Выбрать"), + new ExpectedToken(1, 22, 2, SemanticTokenTypes.Keyword, "из"), + new ExpectedToken(1, 25, 10, SemanticTokenTypes.Namespace, "Справочник"), + new ExpectedToken(1, 36, 11, SemanticTokenTypes.Class, "Контрагенты") + )); } @Test @@ -467,14 +439,12 @@ void testQueryWithParameter() { """; // when - var tokens = getTokens(bsl); - - // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); + var decoded = helper.getDecodedTokens(bsl, supplier); - // &Параметр - один объединённый токен - assertThat(parameterTokens).hasSize(1); - assertThat(parameterTokens.get(0).line()).isEqualTo(1); + // then - Query parameter has readonly modifier + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 32, 9, SemanticTokenTypes.Parameter, SemanticTokenModifiers.Readonly, "&Параметр") + )); } @Test @@ -487,13 +457,12 @@ void testQueryWithVirtualTable() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var methodTokens = tokensOfType(tokens, SemanticTokenTypes.Method); - - // СрезПоследних - assertThat(methodTokens).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 52, 13, SemanticTokenTypes.Method, "СрезПоследних") + )); } @Test @@ -506,19 +475,14 @@ void testQueryWithValueFunction() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var namespaceTokens = tokensOfType(tokens, SemanticTokenTypes.Namespace); - var classTokens = tokensOfType(tokens, SemanticTokenTypes.Class); - var enumMemberTokens = tokensOfType(tokens, SemanticTokenTypes.EnumMember); - - // Справочник - assertThat(namespaceTokens).hasSize(1); - // Валюты - assertThat(classTokens).hasSize(1); - // Рубль - assertThat(enumMemberTokens).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 44, 10, SemanticTokenTypes.Namespace, "Справочник"), + new ExpectedToken(1, 55, 6, SemanticTokenTypes.Class, "Валюты"), + new ExpectedToken(1, 62, 5, SemanticTokenTypes.EnumMember, "Рубль") + )); } @Test @@ -531,16 +495,14 @@ void testQueryWithEnumValue() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var enumTokens = tokensOfType(tokens, SemanticTokenTypes.Enum); - var enumMemberTokens = tokensOfType(tokens, SemanticTokenTypes.EnumMember); - - // Пол (enum) - assertThat(enumTokens).hasSize(1); - // Мужской (enum member) - assertThat(enumMemberTokens).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 41, 12, SemanticTokenTypes.Namespace, "Перечисление"), + new ExpectedToken(1, 54, 3, SemanticTokenTypes.Enum, "Пол"), + new ExpectedToken(1, 58, 7, SemanticTokenTypes.EnumMember, "Мужской") + )); } @Test @@ -553,13 +515,12 @@ void testQueryWithAggregateFunction() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); - // then - var functionTokens = tokensOfType(tokens, SemanticTokenTypes.Function); - - // Количество - assertThat(functionTokens).hasSize(1); + // then - Aggregate function has defaultLibrary modifier + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 20, 10, SemanticTokenTypes.Function, SemanticTokenModifiers.DefaultLibrary, "Количество") + )); } // ==================== Configurable Template Function Tests ==================== @@ -574,16 +535,13 @@ void testSubstituteParametersToStringPlaceholders() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 - assertThat(parameterTokens).hasSize(2); - - // Check that string parts are also present - var stringTokens = tokensOfType(tokens, SemanticTokenTypes.String); - assertThat(stringTokens).isNotEmpty(); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 81, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 93, 2, SemanticTokenTypes.Parameter, "%2") + )); } @Test @@ -596,12 +554,13 @@ void testSubstituteParametersToStringEnglish() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 - assertThat(parameterTokens).hasSize(2); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 73, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 86, 2, SemanticTokenTypes.Parameter, "%2") + )); } @Test @@ -614,12 +573,13 @@ void testSubstituteParametersToStringLocal() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2 - assertThat(parameterTokens).hasSize(2); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 52, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 64, 2, SemanticTokenTypes.Parameter, "%2") + )); } @Test @@ -633,12 +593,14 @@ void testSubstituteParametersToStringWithVariable() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - placeholders in the assigned string should be highlighted - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1, %2, %3 - assertThat(parameterTokens).hasSize(3); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 12, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 17, 2, SemanticTokenTypes.Parameter, "%2"), + new ExpectedToken(1, 22, 2, SemanticTokenTypes.Parameter, "%3") + )); } @Test @@ -652,15 +614,12 @@ void testNStrWithSubstituteParametersToString() { """; // when - var tokens = getTokens(bsl); + var decoded = helper.getDecodedTokens(bsl, supplier); // then - should have both language keys (ru) and placeholders (%1) - var propertyTokens = tokensOfType(tokens, SemanticTokenTypes.Property); - // ru - assertThat(propertyTokens).hasSize(1); - - var parameterTokens = tokensOfType(tokens, SemanticTokenTypes.Parameter); - // %1 - assertThat(parameterTokens).hasSize(1); + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 17, 2, SemanticTokenTypes.Property, "ru"), + new ExpectedToken(1, 30, 2, SemanticTokenTypes.Parameter, "%1") + )); } } \ No newline at end of file From e8e067d7f05117b069ca60a22e656cc5534a0de0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:06:35 +0000 Subject: [PATCH 11/13] Add tests for case-insensitive method name matching Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../StringSemanticTokensSupplierTest.java | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java index 3343903de2b..37b4e9cd23c 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplierTest.java @@ -622,4 +622,158 @@ void testNStrWithSubstituteParametersToString() { new ExpectedToken(1, 30, 2, SemanticTokenTypes.Parameter, "%1") )); } + + // ==================== Case-Insensitive Method Name Tests ==================== + + @Test + void testStrTemplateUpperCase() { + // given - СтрШаблон in uppercase + String bsl = """ + Процедура Тест() + Текст = СТРШАБЛОН("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then - placeholders should still be highlighted + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 35, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 47, 2, SemanticTokenTypes.Parameter, "%2") + )); + } + + @Test + void testStrTemplateMixedCase() { + // given - СтрШаблон in mixed case + String bsl = """ + Процедура Тест() + Текст = стрШаблон("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 35, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 47, 2, SemanticTokenTypes.Parameter, "%2") + )); + } + + @Test + void testNStrUpperCase() { + // given - НСтр in uppercase + String bsl = """ + Процедура Тест() + Текст = НСТР("ru='Привет'; en='Hello'"); + КонецПроцедуры + """; + + // when + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then - language keys should still be highlighted + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 16, 2, SemanticTokenTypes.Property, "ru"), + new ExpectedToken(1, 29, 2, SemanticTokenTypes.Property, "en") + )); + } + + @Test + void testSubstituteParametersToStringUpperCase() { + // given - local method call in uppercase + String bsl = """ + Процедура Тест() + Текст = ПОДСТАВИТЬПАРАМЕТРЫВСТРОКУ("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then - placeholders should be highlighted + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 52, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 64, 2, SemanticTokenTypes.Parameter, "%2") + )); + } + + @Test + void testSubstituteParametersToStringMixedCase() { + // given - local method call in mixed case + String bsl = """ + Процедура Тест() + Текст = подставитьПараметрыВСтроку("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 52, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 64, 2, SemanticTokenTypes.Parameter, "%2") + )); + } + + @Test + void testModuleMethodCallUpperCase() { + // given - module.method call in uppercase + String bsl = """ + Процедура Тест() + Текст = СТРОКОВЫЕФУНКЦИИКЛИЕНТСЕРВЕР.ПОДСТАВИТЬПАРАМЕТРЫВСТРОКУ("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 81, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 93, 2, SemanticTokenTypes.Parameter, "%2") + )); + } + + @Test + void testModuleMethodCallMixedCase() { + // given - module.method call in mixed case + String bsl = """ + Процедура Тест() + Текст = СтроковыеФункцииКлиентсервер.подставитьПараметрыВстроку("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + // when + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 81, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 93, 2, SemanticTokenTypes.Parameter, "%2") + )); + } + + @Test + void testEnglishSubstituteParametersToStringUpperCase() { + // given - English variant in uppercase + String bsl = """ + Процедура Тест() + Text = STRINGFUNCTIONSCLIENTSERVER.SUBSTITUTEPARAMETERSTOSTRING("Name: %1, version: %2", Name, Version); + КонецПроцедуры + """; + + // when + var decoded = helper.getDecodedTokens(bsl, supplier); + + // then + helper.assertContainsTokens(decoded, List.of( + new ExpectedToken(1, 73, 2, SemanticTokenTypes.Parameter, "%1"), + new ExpectedToken(1, 86, 2, SemanticTokenTypes.Parameter, "%2") + )); + } } \ No newline at end of file From bc8faac4709510a5f5f86bf02de1fedd276bb5e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:49:47 +0000 Subject: [PATCH 12/13] Add setter-based caching for SemanticTokensOptions and unit tests Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../semantictokens/SemanticTokensOptions.java | 49 +++-- .../StringSemanticTokensSupplier.java | 2 +- .../LanguageServerConfigurationTest.java | 10 + .../SemanticTokensOptionsTest.java | 202 ++++++++++++++++++ .../.partial-bsl-language-server.json | 6 + 5 files changed, 256 insertions(+), 13 deletions(-) create mode 100644 src/test/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptionsTest.java diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java index 009178f4224..cbeff2e071a 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java @@ -25,7 +25,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; -import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; import java.util.ArrayList; @@ -42,12 +42,22 @@ * Позволяет указать дополнительные функции-шаблонизаторы строк, * аналогичные СтрШаблон/StrTemplate, для подсветки плейсхолдеров (%1, %2 и т.д.). */ -@Data +@Getter @AllArgsConstructor(onConstructor = @__({@JsonCreator(mode = JsonCreator.Mode.DISABLED)})) @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class SemanticTokensOptions { + private static final List DEFAULT_STR_TEMPLATE_METHODS = List.of( + // Локальный вызов + "ПодставитьПараметрыВСтроку", + "SubstituteParametersToString", + // Стандартный модуль БСП + "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", + // Английский вариант + "StringFunctionsClientServer.SubstituteParametersToString" + ); + /** * Список паттернов "Модуль.Метод" для функций-шаблонизаторов строк. *

    @@ -63,15 +73,23 @@ public class SemanticTokensOptions { *

    * По умолчанию включает стандартные варианты из БСП. */ - private List strTemplateMethods = new ArrayList<>(List.of( - // Локальный вызов - "ПодставитьПараметрыВСтроку", - "SubstituteParametersToString", - // Стандартный модуль БСП - "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", - // Английский вариант - "StringFunctionsClientServer.SubstituteParametersToString" - )); + private List strTemplateMethods = new ArrayList<>(DEFAULT_STR_TEMPLATE_METHODS); + + /** + * Кэшированные разобранные паттерны функций-шаблонизаторов. + */ + @JsonIgnore + private ParsedStrTemplateMethods parsedStrTemplateMethods; + + /** + * Устанавливает список паттернов функций-шаблонизаторов и пересчитывает кэш. + * + * @param strTemplateMethods Список паттернов + */ + public void setStrTemplateMethods(List strTemplateMethods) { + this.strTemplateMethods = strTemplateMethods; + this.parsedStrTemplateMethods = parseStrTemplateMethods(strTemplateMethods); + } /** * Возвращает предварительно разобранные паттерны функций-шаблонизаторов. @@ -80,10 +98,17 @@ public class SemanticTokensOptions { */ @JsonIgnore public ParsedStrTemplateMethods getParsedStrTemplateMethods() { + if (parsedStrTemplateMethods == null) { + parsedStrTemplateMethods = parseStrTemplateMethods(strTemplateMethods); + } + return parsedStrTemplateMethods; + } + + private static ParsedStrTemplateMethods parseStrTemplateMethods(List methods) { var localMethods = new HashSet(); var moduleMethodPairs = new HashMap>(); - for (var pattern : strTemplateMethods) { + for (var pattern : methods) { if (pattern.isBlank()) { continue; } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java index 6f9da5706df..20d98356269 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/StringSemanticTokensSupplier.java @@ -80,7 +80,7 @@ public class StringSemanticTokensSupplier implements SemanticTokensSupplier { private final SemanticTokensHelper helper; private final LanguageServerConfiguration configuration; - private ParsedStrTemplateMethods parsedStrTemplateMethods; + private volatile ParsedStrTemplateMethods parsedStrTemplateMethods; @PostConstruct private void init() { diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfigurationTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfigurationTest.java index 40e03ba2f5d..16178676ece 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfigurationTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/configuration/LanguageServerConfigurationTest.java @@ -26,6 +26,7 @@ import com.github._1c_syntax.bsl.languageserver.configuration.diagnostics.Mode; import com.github._1c_syntax.bsl.languageserver.configuration.diagnostics.SkipSupport; import com.github._1c_syntax.bsl.languageserver.configuration.inlayhints.InlayHintOptions; +import com.github._1c_syntax.bsl.languageserver.configuration.semantictokens.SemanticTokensOptions; import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterEachTestMethod; import com.github._1c_syntax.utils.Absolute; import org.eclipse.lsp4j.TextDocumentSyncKind; @@ -162,6 +163,7 @@ void testPartialInitialization() { CodeLensOptions codeLensOptions = configuration.getCodeLensOptions(); DiagnosticsOptions diagnosticsOptions = configuration.getDiagnosticsOptions(); InlayHintOptions inlayHintOptions = configuration.getInlayHintOptions(); + SemanticTokensOptions semanticTokensOptions = configuration.getSemanticTokensOptions(); // then assertThat(codeLensOptions.getParameters().get("cognitiveComplexity")).isNull(); @@ -176,6 +178,14 @@ void testPartialInitialization() { assertThat(inlayHintOptions.getParameters()) .containsEntry("sourceDefinedMethodCall", Either.forRight(Map.of("showParametersWithTheSameName", true))); + assertThat(semanticTokensOptions.getStrTemplateMethods()) + .hasSize(2) + .contains("CustomModule.CustomMethod", "CustomLocalMethod"); + assertThat(semanticTokensOptions.getParsedStrTemplateMethods().localMethods()) + .contains("customlocalmethod"); + assertThat(semanticTokensOptions.getParsedStrTemplateMethods().moduleMethodPairs()) + .containsKey("custommodule"); + } } \ No newline at end of file diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptionsTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptionsTest.java new file mode 100644 index 00000000000..1b3cbf29771 --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptionsTest.java @@ -0,0 +1,202 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2025 + * 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.configuration.semantictokens; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class SemanticTokensOptionsTest { + + @Test + void testDefaultValues() { + // when + var options = new SemanticTokensOptions(); + + // then + assertThat(options.getStrTemplateMethods()) + .hasSize(4) + .contains( + "ПодставитьПараметрыВСтроку", + "SubstituteParametersToString", + "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", + "StringFunctionsClientServer.SubstituteParametersToString" + ); + } + + @Test + void testParseLocalMethods() { + // given + var options = new SemanticTokensOptions(); + options.setStrTemplateMethods(List.of( + "ПодставитьПараметрыВСтроку", + "SubstituteParametersToString", + "CustomMethod" + )); + + // when + var parsed = options.getParsedStrTemplateMethods(); + + // then + assertThat(parsed.localMethods()) + .hasSize(3) + .contains("подставитьпараметрывстроку", "substituteparameterstostring", "custommethod"); + assertThat(parsed.moduleMethodPairs()).isEmpty(); + } + + @Test + void testParseModuleMethodPairs() { + // given + var options = new SemanticTokensOptions(); + options.setStrTemplateMethods(List.of( + "СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку", + "StringFunctionsClientServer.SubstituteParametersToString", + "MyModule.MyMethod" + )); + + // when + var parsed = options.getParsedStrTemplateMethods(); + + // then + assertThat(parsed.localMethods()).isEmpty(); + assertThat(parsed.moduleMethodPairs()) + .hasSize(3) + .containsKeys("строковыефункцииклиентсервер", "stringfunctionsclientserver", "mymodule"); + assertThat(parsed.moduleMethodPairs().get("строковыефункцииклиентсервер")) + .contains("подставитьпараметрывстроку"); + assertThat(parsed.moduleMethodPairs().get("stringfunctionsclientserver")) + .contains("substituteparameterstostring"); + assertThat(parsed.moduleMethodPairs().get("mymodule")) + .contains("mymethod"); + } + + @Test + void testCaseInsensitiveMatching() { + // given + var options = new SemanticTokensOptions(); + options.setStrTemplateMethods(List.of( + "ПОДСТАВИТЬПАРАМЕТРЫВСТРОКУ", + "СтроковыеФункцииКлиентСервер.ПОДСТАВИТЬПАРАМЕТРЫВСТРОКУ" + )); + + // when + var parsed = options.getParsedStrTemplateMethods(); + + // then + assertThat(parsed.localMethods()) + .hasSize(1) + .contains("подставитьпараметрывстроку"); + assertThat(parsed.moduleMethodPairs().get("строковыефункцииклиентсервер")) + .contains("подставитьпараметрывстроку"); + } + + @Test + void testEmptyStrings() { + // given + var options = new SemanticTokensOptions(); + options.setStrTemplateMethods(List.of( + "", + " ", + "ValidMethod" + )); + + // when + var parsed = options.getParsedStrTemplateMethods(); + + // then + assertThat(parsed.localMethods()) + .hasSize(1) + .contains("validmethod"); + assertThat(parsed.moduleMethodPairs()).isEmpty(); + } + + @Test + void testInvalidPatterns() { + // given + var options = new SemanticTokensOptions(); + options.setStrTemplateMethods(List.of( + ".InvalidPattern", + "InvalidPattern.", + "..DoubleDot", + "Valid.Pattern" + )); + + // when + var parsed = options.getParsedStrTemplateMethods(); + + // then + // Invalid patterns should not create entries + assertThat(parsed.localMethods()).isEmpty(); + assertThat(parsed.moduleMethodPairs()) + .hasSize(1) + .containsKey("valid"); + assertThat(parsed.moduleMethodPairs().get("valid")) + .contains("pattern"); + } + + @Test + void testCachingBehavior() { + // given + var options = new SemanticTokensOptions(); + options.setStrTemplateMethods(List.of("Method1")); + + // when + var parsed1 = options.getParsedStrTemplateMethods(); + var parsed2 = options.getParsedStrTemplateMethods(); + + // then + assertThat(parsed1).isSameAs(parsed2); + + // Update methods + options.setStrTemplateMethods(List.of("Method2")); + var parsed3 = options.getParsedStrTemplateMethods(); + + // Cache should be updated + assertThat(parsed3).isNotSameAs(parsed1); + assertThat(parsed3.localMethods()) + .hasSize(1) + .contains("method2"); + } + + @Test + void testPatternsWithMultipleDots() { + // given + var options = new SemanticTokensOptions(); + options.setStrTemplateMethods(List.of( + "Module.SubModule.Method" + )); + + // when + var parsed = options.getParsedStrTemplateMethods(); + + // then + // Should split only on first dot + assertThat(parsed.localMethods()).isEmpty(); + assertThat(parsed.moduleMethodPairs()) + .hasSize(1) + .containsKey("module"); + assertThat(parsed.moduleMethodPairs().get("module")) + .contains("submodule.method"); + } +} diff --git a/src/test/resources/.partial-bsl-language-server.json b/src/test/resources/.partial-bsl-language-server.json index 070725801a3..110edf68a84 100644 --- a/src/test/resources/.partial-bsl-language-server.json +++ b/src/test/resources/.partial-bsl-language-server.json @@ -13,5 +13,11 @@ }, "diagnostics": { "mode": "on" + }, + "semanticTokens": { + "strTemplateMethods": [ + "CustomModule.CustomMethod", + "CustomLocalMethod" + ] } } From 8aec16e6b0b2f6d0e4b4bb3de15376a160f4f03e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:59:47 +0000 Subject: [PATCH 13/13] Initialize parsedStrTemplateMethods field directly to avoid null check Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../configuration/semantictokens/SemanticTokensOptions.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java index cbeff2e071a..48507252dd4 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/configuration/semantictokens/SemanticTokensOptions.java @@ -79,7 +79,7 @@ public class SemanticTokensOptions { * Кэшированные разобранные паттерны функций-шаблонизаторов. */ @JsonIgnore - private ParsedStrTemplateMethods parsedStrTemplateMethods; + private ParsedStrTemplateMethods parsedStrTemplateMethods = parseStrTemplateMethods(DEFAULT_STR_TEMPLATE_METHODS); /** * Устанавливает список паттернов функций-шаблонизаторов и пересчитывает кэш. @@ -98,9 +98,6 @@ public void setStrTemplateMethods(List strTemplateMethods) { */ @JsonIgnore public ParsedStrTemplateMethods getParsedStrTemplateMethods() { - if (parsedStrTemplateMethods == null) { - parsedStrTemplateMethods = parseStrTemplateMethods(strTemplateMethods); - } return parsedStrTemplateMethods; }