From 0574b8baeeb55f81e66c38450f76dee664d3bed1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 10:08:10 +0000 Subject: [PATCH 1/3] Initial plan From c3b421f86662e1228307c5f26faf3f450490f0e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 10:19:31 +0000 Subject: [PATCH 2/3] Add NStr and StrTemplate semantic highlighting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement NStrAndStrTemplateSemanticTokensSupplier to highlight: - Language keys (ru=, en=) in NStr/НСтр function calls - Placeholders (%1, %2, %(1)) in StrTemplate/СтрШаблон function calls Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- ...rAndStrTemplateSemanticTokensSupplier.java | 185 ++++++++++++ ...StrTemplateSemanticTokensSupplierTest.java | 274 ++++++++++++++++++ 2 files changed, 459 insertions(+) create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplier.java create mode 100644 src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplierTest.java diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplier.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplier.java new file mode 100644 index 00000000000..6e8549694df --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplier.java @@ -0,0 +1,185 @@ +/* + * 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.semantictokens; + +import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; +import com.github._1c_syntax.bsl.languageserver.utils.Trees; +import com.github._1c_syntax.bsl.parser.BSLParser; +import com.github._1c_syntax.bsl.parser.BSLParserBaseVisitor; +import com.github._1c_syntax.utils.CaseInsensitivePattern; +import lombok.RequiredArgsConstructor; +import org.antlr.v4.runtime.Token; +import org.eclipse.lsp4j.SemanticTokenTypes; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Сапплаер семантических токенов для функций НСтр (NStr) и СтрШаблон (StrTemplate). + *

+ * Для НСтр: подсвечивает языковые ключи (ru=, en=) в строковых параметрах. + *

+ * Для СтрШаблон: подсвечивает плейсхолдеры (%1, %2, %(1)) в строковых параметрах. + */ +@Component +@RequiredArgsConstructor +public class NStrAndStrTemplateSemanticTokensSupplier implements SemanticTokensSupplier { + + private static final Pattern NSTR_METHOD_PATTERN = CaseInsensitivePattern.compile("^(НСтр|NStr)$"); + private static final Pattern TEMPLATE_METHOD_PATTERN = CaseInsensitivePattern.compile("^(СтрШаблон|StrTemplate)$"); + + // Pattern for NStr language keys like "ru='..." or "en='..." + // Matches language code followed by = and either ' or " + private static final Pattern NSTR_LANG_KEY_PATTERN = Pattern.compile("(\\w+)\\s*=\\s*['\"]"); + + // Pattern for StrTemplate placeholders: %1-%10 or %(1)-%(10) + private static final Pattern STR_TEMPLATE_PLACEHOLDER_PATTERN = Pattern.compile("%(?:(10|[1-9])|\\((10|[1-9])\\))"); + + private static final Set STRING_TOKEN_TYPES = Set.of( + BSLParser.STRING, + BSLParser.STRINGPART, + BSLParser.STRINGSTART, + BSLParser.STRINGTAIL + ); + + private final SemanticTokensHelper helper; + + @Override + public List getSemanticTokens(DocumentContext documentContext) { + List entries = new ArrayList<>(); + + var visitor = new NStrAndStrTemplateVisitor(entries, helper); + visitor.visit(documentContext.getAst()); + + return entries; + } + + /** + * Visitor for finding NStr and StrTemplate method calls. + */ + private static class NStrAndStrTemplateVisitor extends BSLParserBaseVisitor { + private final List entries; + private final SemanticTokensHelper helper; + + public NStrAndStrTemplateVisitor(List entries, SemanticTokensHelper helper) { + this.entries = entries; + this.helper = helper; + } + + @Override + public Void visitGlobalMethodCall(BSLParser.GlobalMethodCallContext ctx) { + String methodName = ctx.methodName().getText(); + + if (NSTR_METHOD_PATTERN.matcher(methodName).matches()) { + processNStrCall(ctx); + } else if (TEMPLATE_METHOD_PATTERN.matcher(methodName).matches()) { + processStrTemplateCall(ctx); + } + + return super.visitGlobalMethodCall(ctx); + } + + private void processNStrCall(BSLParser.GlobalMethodCallContext ctx) { + var callParams = ctx.doCall().callParamList().callParam(); + if (callParams.isEmpty()) { + return; + } + + // Get the first parameter (the multilingual string) + var firstParam = callParams.get(0); + var stringTokens = getStringTokens(firstParam); + + for (Token token : stringTokens) { + String tokenText = token.getText(); + int tokenLine = token.getLine() - 1; // Convert to 0-indexed + int tokenStart = token.getCharPositionInLine(); + + // Find language keys in the string + Matcher matcher = NSTR_LANG_KEY_PATTERN.matcher(tokenText); + while (matcher.find()) { + String langKey = matcher.group(1); + int keyStart = matcher.start(1); + int keyLength = langKey.length(); + + // Add semantic token for the language key + helper.addEntry( + entries, + tokenLine, + tokenStart + keyStart, + keyLength, + SemanticTokenTypes.Property + ); + } + } + } + + private void processStrTemplateCall(BSLParser.GlobalMethodCallContext ctx) { + var callParams = ctx.doCall().callParamList().callParam(); + if (callParams.isEmpty()) { + return; + } + + // Get the first parameter (the template string) + var firstParam = callParams.get(0); + var stringTokens = getStringTokens(firstParam); + + for (Token token : stringTokens) { + String tokenText = token.getText(); + int tokenLine = token.getLine() - 1; // Convert to 0-indexed + int tokenStart = token.getCharPositionInLine(); + + // Find placeholders in the string + Matcher matcher = STR_TEMPLATE_PLACEHOLDER_PATTERN.matcher(tokenText); + while (matcher.find()) { + int placeholderStart = matcher.start(); + int placeholderLength = matcher.group().length(); + + // Add semantic token for the placeholder + helper.addEntry( + entries, + tokenLine, + tokenStart + placeholderStart, + placeholderLength, + SemanticTokenTypes.Parameter + ); + } + } + } + + private List getStringTokens(BSLParser.CallParamContext callParam) { + List stringTokens = new ArrayList<>(); + var tokens = Trees.getTokens(callParam); + + for (Token token : tokens) { + if (STRING_TOKEN_TYPES.contains(token.getType())) { + stringTokens.add(token); + } + } + + return stringTokens; + } + } +} diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplierTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplierTest.java new file mode 100644 index 00000000000..da5f12b64f3 --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplierTest.java @@ -0,0 +1,274 @@ +/* + * 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.semantictokens; + +import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterEachTestMethod; +import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +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 static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@CleanupContextBeforeClassAndAfterEachTestMethod +class NStrAndStrTemplateSemanticTokensSupplierTest { + + @Autowired + private NStrAndStrTemplateSemanticTokensSupplier supplier; + + @Autowired + private SemanticTokensLegend legend; + + @Test + void testNStrLanguageKeys() { + // given + String bsl = """ + Процедура Тест() + Текст = НСтр("ru='Привет'; en='Hello'"); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + int propertyTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Property); + var propertyTokens = tokens.stream() + .filter(t -> t.type() == propertyTypeIdx) + .toList(); + // ru, en + assertThat(propertyTokens).hasSize(2); + } + + @Test + void testNStrEnglishName() { + // given + String bsl = """ + Процедура Тест() + Текст = NStr("ru='Привет'; en='Hello'"); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + int propertyTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Property); + var propertyTokens = tokens.stream() + .filter(t -> t.type() == propertyTypeIdx) + .toList(); + // ru, en + assertThat(propertyTokens).hasSize(2); + } + + @Test + void testStrTemplatePlaceholders() { + // given + String bsl = """ + Процедура Тест() + Текст = СтрШаблон("Наименование: %1, версия: %2", Наименование, Версия); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + int parameterTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Parameter); + var parameterTokens = tokens.stream() + .filter(t -> t.type() == parameterTypeIdx) + .toList(); + // %1, %2 + assertThat(parameterTokens).hasSize(2); + } + + @Test + void testStrTemplateEnglishName() { + // given + String bsl = """ + Процедура Тест() + Текст = StrTemplate("Name: %1, version: %2", Name, Version); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + int parameterTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Parameter); + var parameterTokens = tokens.stream() + .filter(t -> t.type() == parameterTypeIdx) + .toList(); + // %1, %2 + assertThat(parameterTokens).hasSize(2); + } + + @Test + void testStrTemplatePlaceholdersWithParentheses() { + // given + String bsl = """ + Процедура Тест() + Текст = СтрШаблон("%(1)%(2)", "Первая", "Вторая"); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + int parameterTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Parameter); + var parameterTokens = tokens.stream() + .filter(t -> t.type() == parameterTypeIdx) + .toList(); + // %(1), %(2) + assertThat(parameterTokens).hasSize(2); + } + + @Test + void testStrTemplateWithPlaceholder10() { + // given + String bsl = """ + Процедура Тест() + Текст = СтрШаблон("%1 %2 %3 %4 %5 %6 %7 %8 %9 %10", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + int parameterTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Parameter); + var parameterTokens = tokens.stream() + .filter(t -> t.type() == parameterTypeIdx) + .toList(); + // %1 through %10 + assertThat(parameterTokens).hasSize(10); + } + + @Test + void testNStrWithNestedStrTemplate() { + // given + String bsl = """ + Процедура Тест() + Текст = СтрШаблон(НСтр("ru='Наименование: %1'"), Наименование); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + int propertyTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Property); + int parameterTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Parameter); + + var propertyTokens = tokens.stream() + .filter(t -> t.type() == propertyTypeIdx) + .toList(); + var parameterTokens = tokens.stream() + .filter(t -> t.type() == parameterTypeIdx) + .toList(); + + // ru + assertThat(propertyTokens).hasSize(1); + // %1 + assertThat(parameterTokens).hasSize(1); + } + + @Test + void testMultilineNStr() { + // given + String bsl = """ + Процедура Тест() + Текст = НСтр("ru='Первая строка + |Вторая строка'; en='First line + |Second line'"); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + int propertyTypeIdx = legend.getTokenTypes().indexOf(SemanticTokenTypes.Property); + var propertyTokens = tokens.stream() + .filter(t -> t.type() == propertyTypeIdx) + .toList(); + // ru, en - language keys are in the first string token + assertThat(propertyTokens).hasSizeGreaterThanOrEqualTo(1); + } + + @Test + void testEmptyParams() { + // given - no crash when there are no parameters + String bsl = """ + Процедура Тест() + Текст = НСтр(); + Текст2 = СтрШаблон(); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + assertThat(tokens).isEmpty(); + } + + @Test + void testNoHighlightForOtherMethods() { + // given + String bsl = """ + Процедура Тест() + Текст = Сообщить("ru='Привет' %1"); + КонецПроцедуры + """; + + var documentContext = TestUtils.getDocumentContext(bsl); + + // when + var tokens = supplier.getSemanticTokens(documentContext); + + // then + assertThat(tokens).isEmpty(); + } +} From 5c618ee7ea61cab6d52441e8f1e3adae88072554 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 12:37:05 +0000 Subject: [PATCH 3/3] Refactor to use MultilingualStringAnalyser for NStr/StrTemplate highlighting - Extended MultilingualStringAnalyser with public patterns and utility methods: - isNStrCall() and isStrTemplateCall() static methods - findLanguageKeyPositions() and findPlaceholderPositions() static methods - MatchPosition record for position information - STR_TEMPLATE_PLACEHOLDER_PATTERN for placeholder detection - Updated NStrAndStrTemplateSemanticTokensSupplier to use MultilingualStringAnalyser - Removed duplicate patterns from semantic tokens supplier Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- ...rAndStrTemplateSemanticTokensSupplier.java | 49 +++----- .../utils/MultilingualStringAnalyser.java | 109 ++++++++++++++++-- 2 files changed, 114 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplier.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplier.java index 6e8549694df..95243b35264 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplier.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/semantictokens/NStrAndStrTemplateSemanticTokensSupplier.java @@ -22,10 +22,10 @@ package com.github._1c_syntax.bsl.languageserver.semantictokens; import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; +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.BSLParser; import com.github._1c_syntax.bsl.parser.BSLParserBaseVisitor; -import com.github._1c_syntax.utils.CaseInsensitivePattern; import lombok.RequiredArgsConstructor; import org.antlr.v4.runtime.Token; import org.eclipse.lsp4j.SemanticTokenTypes; @@ -34,8 +34,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Сапплаер семантических токенов для функций НСтр (NStr) и СтрШаблон (StrTemplate). @@ -48,16 +46,6 @@ @RequiredArgsConstructor public class NStrAndStrTemplateSemanticTokensSupplier implements SemanticTokensSupplier { - private static final Pattern NSTR_METHOD_PATTERN = CaseInsensitivePattern.compile("^(НСтр|NStr)$"); - private static final Pattern TEMPLATE_METHOD_PATTERN = CaseInsensitivePattern.compile("^(СтрШаблон|StrTemplate)$"); - - // Pattern for NStr language keys like "ru='..." or "en='..." - // Matches language code followed by = and either ' or " - private static final Pattern NSTR_LANG_KEY_PATTERN = Pattern.compile("(\\w+)\\s*=\\s*['\"]"); - - // Pattern for StrTemplate placeholders: %1-%10 or %(1)-%(10) - private static final Pattern STR_TEMPLATE_PLACEHOLDER_PATTERN = Pattern.compile("%(?:(10|[1-9])|\\((10|[1-9])\\))"); - private static final Set STRING_TOKEN_TYPES = Set.of( BSLParser.STRING, BSLParser.STRINGPART, @@ -91,11 +79,9 @@ public NStrAndStrTemplateVisitor(List entries, SemanticToken @Override public Void visitGlobalMethodCall(BSLParser.GlobalMethodCallContext ctx) { - String methodName = ctx.methodName().getText(); - - if (NSTR_METHOD_PATTERN.matcher(methodName).matches()) { + if (MultilingualStringAnalyser.isNStrCall(ctx)) { processNStrCall(ctx); - } else if (TEMPLATE_METHOD_PATTERN.matcher(methodName).matches()) { + } else if (MultilingualStringAnalyser.isStrTemplateCall(ctx)) { processStrTemplateCall(ctx); } @@ -117,19 +103,14 @@ private void processNStrCall(BSLParser.GlobalMethodCallContext ctx) { int tokenLine = token.getLine() - 1; // Convert to 0-indexed int tokenStart = token.getCharPositionInLine(); - // Find language keys in the string - Matcher matcher = NSTR_LANG_KEY_PATTERN.matcher(tokenText); - while (matcher.find()) { - String langKey = matcher.group(1); - int keyStart = matcher.start(1); - int keyLength = langKey.length(); - - // Add semantic token for the language key + // Find language keys in the string using MultilingualStringAnalyser + var positions = MultilingualStringAnalyser.findLanguageKeyPositions(tokenText); + for (var position : positions) { helper.addEntry( entries, tokenLine, - tokenStart + keyStart, - keyLength, + tokenStart + position.start(), + position.length(), SemanticTokenTypes.Property ); } @@ -151,18 +132,14 @@ private void processStrTemplateCall(BSLParser.GlobalMethodCallContext ctx) { int tokenLine = token.getLine() - 1; // Convert to 0-indexed int tokenStart = token.getCharPositionInLine(); - // Find placeholders in the string - Matcher matcher = STR_TEMPLATE_PLACEHOLDER_PATTERN.matcher(tokenText); - while (matcher.find()) { - int placeholderStart = matcher.start(); - int placeholderLength = matcher.group().length(); - - // Add semantic token for the placeholder + // Find placeholders in the string using MultilingualStringAnalyser + var positions = MultilingualStringAnalyser.findPlaceholderPositions(tokenText); + for (var position : positions) { helper.addEntry( entries, tokenLine, - tokenStart + placeholderStart, - placeholderLength, + tokenStart + position.start(), + position.length(), SemanticTokenTypes.Parameter ); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/MultilingualStringAnalyser.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/MultilingualStringAnalyser.java index da4a693a923..983326f7e1f 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/MultilingualStringAnalyser.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/MultilingualStringAnalyser.java @@ -29,33 +29,58 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Анализатор многоязычных строк НСтр (NStr). + * Анализатор многоязычных строк НСтр (NStr) и строковых шаблонов СтрШаблон (StrTemplate). *

* Проверяет наличие всех объявленных языков в многоязычных строках * и анализирует использование в шаблонах. + *

+ * Также предоставляет статические методы для поиска языковых ключей и плейсхолдеров + * в строках для целей семантической подсветки. */ public final class MultilingualStringAnalyser { - private static final String NSTR_METHOD_NAME = "^(НСтр|NStr)"; - private static final String TEMPLATE_METHOD_NAME = "^(СтрШаблон|StrTemplate)"; + private static final String NSTR_METHOD_NAME = "^(НСтр|NStr)$"; + private static final String TEMPLATE_METHOD_NAME = "^(СтрШаблон|StrTemplate)$"; private static final String NSTR_LANG_REGEX = "\\w+\\s*=\\s*['|\"{2}]"; private static final String NSTR_LANG_CUT_REGEX = "\\s*=\\s*['|\"{2}]"; private static final String WHITE_SPACE_REGEX = "\\s"; - private static final Pattern NSTR_METHOD_NAME_PATTERN = CaseInsensitivePattern.compile( + + /** + * Паттерн для распознавания вызова метода НСтр/NStr. + */ + public static final Pattern NSTR_METHOD_NAME_PATTERN = CaseInsensitivePattern.compile( NSTR_METHOD_NAME ); - private static final Pattern TEMPLATE_METHOD_NAME_PATTERN = CaseInsensitivePattern.compile( + + /** + * Паттерн для распознавания вызова метода СтрШаблон/StrTemplate. + */ + public static final Pattern TEMPLATE_METHOD_NAME_PATTERN = CaseInsensitivePattern.compile( TEMPLATE_METHOD_NAME ); - private static final Pattern NSTR_LANG_PATTERN = CaseInsensitivePattern.compile( + + /** + * Паттерн для поиска языковых ключей в строках НСтр (например, ru=', en="). + */ + public static final Pattern NSTR_LANG_PATTERN = CaseInsensitivePattern.compile( NSTR_LANG_REGEX ); + + /** + * Паттерн для поиска плейсхолдеров в строках СтрШаблон (%1-%10 или %(1)-%(10)). + * Учитывает экранирование %%. + */ + public static final Pattern STR_TEMPLATE_PLACEHOLDER_PATTERN = Pattern.compile( + "(? findLanguageKeyPositions(String text) { + List positions = new ArrayList<>(); + Matcher matcher = NSTR_LANG_PATTERN.matcher(text); + + while (matcher.find()) { + Matcher cutMatcher = NSTR_LANG_CUT_PATTERN.matcher(matcher.group()); + String langKey = cutMatcher.replaceAll(""); + int keyStart = matcher.start(); + positions.add(new MatchPosition(keyStart, langKey.length(), langKey)); + } + + return positions; + } + + /** + * Найти позиции всех плейсхолдеров в строке СтрШаблон. + * + * @param text Текст строки + * @return Список позиций плейсхолдеров + */ + public static List findPlaceholderPositions(String text) { + List positions = new ArrayList<>(); + Matcher matcher = STR_TEMPLATE_PLACEHOLDER_PATTERN.matcher(text); + + while (matcher.find()) { + String placeholder = matcher.group(); + positions.add(new MatchPosition(matcher.start(), placeholder.length(), placeholder)); + } + + return positions; + } + @SuppressWarnings("NullAway.Init") private BSLParser.GlobalMethodCallContext globalMethodCallContext; private boolean isParentTemplate; @@ -85,7 +178,7 @@ private static boolean isNotMultilingualString(BSLParser.GlobalMethodCallContext String firstParameterMultilingualString = getMultilingualString(globalMethodCallContext); return !(firstParameterMultilingualString.isEmpty() || firstParameterMultilingualString.startsWith("\"")) - || !NSTR_METHOD_NAME_PATTERN.matcher(globalMethodCallContext.methodName().getText()).find(); + || !NSTR_METHOD_NAME_PATTERN.matcher(globalMethodCallContext.methodName().getText()).matches(); } private static boolean hasTemplateInParents(ParserRuleContext globalMethodCallContext) { @@ -103,7 +196,7 @@ private static boolean hasTemplateInParents(ParserRuleContext globalMethodCallCo } private static boolean isTemplate(BSLParser.GlobalMethodCallContext parent) { - return TEMPLATE_METHOD_NAME_PATTERN.matcher(parent.methodName().getText()).find(); + return TEMPLATE_METHOD_NAME_PATTERN.matcher(parent.methodName().getText()).matches(); } private static @Nullable String getVariableName(BSLParser.GlobalMethodCallContext ctx) {