Skip to content

Commit 89370e0

Browse files
authored
Merge pull request #3591 from 1c-syntax/copilot/add-cache-parameter-ignoring
2 parents 87979ed + df42a09 commit 89370e0

File tree

6 files changed

+266
-0
lines changed

6 files changed

+266
-0
lines changed

docs/diagnostics/TransferringParametersBetweenClientAndServer.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,48 @@
9393
КонецФункции
9494
```
9595

96+
## Параметры
97+
98+
### cachedValueNames
99+
100+
Тип: `Строка`
101+
Значение по умолчанию: `` (пустая строка)
102+
103+
Список имен параметров, разделенных запятыми, которые должны игнорироваться диагностикой, если в модуле существует переменная с таким же именем и директивой компиляции `&НаКлиенте`.
104+
105+
Это полезно для кэшируемых значений, которые специально передаются с сервера на клиент для хранения в переменных модуля формы.
106+
107+
Пример:
108+
109+
```json
110+
{
111+
"TransferringParametersBetweenClientAndServer": {
112+
"cachedValueNames": "КэшированныеЗначения,КэшДанных"
113+
}
114+
}
115+
```
116+
117+
Если в коде есть объявление:
118+
119+
```bsl
120+
&НаКлиенте
121+
Перем КэшированныеЗначения; // используется механизмом обработки изменения реквизитов ТЧ
122+
```
123+
124+
То следующий код не будет генерировать замечание:
125+
126+
```bsl
127+
&НаКлиенте
128+
Процедура ПриИзмененииРеквизита()
129+
ОбновитьКэш(КэшированныеЗначения);
130+
КонецПроцедуры
131+
132+
&НаСервере
133+
Процедура ОбновитьКэш(КэшированныеЗначения)
134+
КэшированныеЗначения = ПолучитьДанныеНаСервере();
135+
КонецПроцедуры
136+
```
137+
96138
## Источники
97139
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
98140
<!-- Примеры источников

docs/en/diagnostics/TransferringParametersBetweenClientAndServer.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,49 @@ Return MessageToUser;
101101
102102
EndFunction
103103
```
104+
105+
## Parameters
106+
107+
### cachedValueNames
108+
109+
Type: `String`
110+
Default value: `` (empty string)
111+
112+
Comma-separated list of parameter names that should be ignored by the diagnostic if a variable with the same name and the `&AtClient` compiler directive exists in the module.
113+
114+
This is useful for cached values that are intentionally transferred from the server to the client for storage in form module variables.
115+
116+
Example:
117+
118+
```json
119+
{
120+
"TransferringParametersBetweenClientAndServer": {
121+
"cachedValueNames": "CachedValues,DataCache"
122+
}
123+
}
124+
```
125+
126+
If there is a declaration in the code:
127+
128+
```bsl
129+
&AtClient
130+
Var CachedValues; // used by the tabular section attribute change processing mechanism
131+
```
132+
133+
Then the following code will not generate a remark:
134+
135+
```bsl
136+
&AtClient
137+
Procedure OnAttributeChange()
138+
UpdateCache(CachedValues);
139+
EndProcedure
140+
141+
&AtServer
142+
Procedure UpdateCache(CachedValues)
143+
CachedValues = GetDataOnServer();
144+
EndProcedure
145+
```
146+
104147
## Sources
105148
<!-- It is necessary to provide links to all sources from which information was obtained to create diagnostics -->
106149
<!-- Sample sources

src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/TransferringParametersBetweenClientAndServerDiagnostic.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,29 @@
2626
import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol;
2727
import com.github._1c_syntax.bsl.languageserver.context.symbol.annotations.CompilerDirectiveKind;
2828
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
29+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticParameter;
2930
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity;
3031
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag;
3132
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType;
3233
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex;
3334
import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType;
3435
import com.github._1c_syntax.bsl.languageserver.references.model.Reference;
3536
import com.github._1c_syntax.bsl.languageserver.utils.RelatedInformation;
37+
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
38+
import com.github._1c_syntax.bsl.parser.BSLParser;
3639
import lombok.RequiredArgsConstructor;
40+
import org.antlr.v4.runtime.Token;
3741
import org.eclipse.lsp4j.DiagnosticRelatedInformation;
3842
import org.eclipse.lsp4j.SymbolKind;
3943

44+
import java.util.Arrays;
4045
import java.util.Collection;
4146
import java.util.Collections;
4247
import java.util.EnumSet;
48+
import java.util.HashSet;
4349
import java.util.List;
50+
import java.util.Locale;
51+
import java.util.Map;
4452
import java.util.Optional;
4553
import java.util.Set;
4654
import java.util.stream.Collectors;
@@ -62,9 +70,29 @@ public class TransferringParametersBetweenClientAndServerDiagnostic extends Abst
6270
CompilerDirectiveKind.AT_SERVER,
6371
CompilerDirectiveKind.AT_SERVER_NO_CONTEXT
6472
);
73+
private static final String DEFAULT_CACHED_VALUE_NAMES = "";
6574

6675
private final ReferenceIndex referenceIndex;
6776

77+
@DiagnosticParameter(
78+
type = String.class,
79+
defaultValue = DEFAULT_CACHED_VALUE_NAMES
80+
)
81+
private final Set<String> cachedValueNames = new HashSet<>();
82+
83+
@Override
84+
public void configure(Map<String, Object> configuration) {
85+
this.cachedValueNames.clear();
86+
var cachedValueNamesString =
87+
(String) configuration.getOrDefault("cachedValueNames", DEFAULT_CACHED_VALUE_NAMES);
88+
if (!cachedValueNamesString.isBlank()) {
89+
Arrays.stream(cachedValueNamesString.split(","))
90+
.map(String::trim)
91+
.map(name -> name.toUpperCase(Locale.ENGLISH))
92+
.forEach(this.cachedValueNames::add);
93+
}
94+
}
95+
6896
// Не учитываются вложенные вызовы. Только прямые - клиентский метод вызывает серверный метод напрямую
6997

7098
@Override
@@ -107,10 +135,49 @@ private List<ParameterDefinition> calcNotAssignedParams(MethodSymbol method) {
107135
private List<ParameterDefinition> calcNotAssignedParams(MethodSymbol method,
108136
List<ParameterDefinition> parameterDefinitions) {
109137
return parameterDefinitions.stream()
138+
.filter(parameterDefinition -> !isCachedValueParameter(parameterDefinition))
110139
.filter(parameterDefinition -> isAssignedParam(method, parameterDefinition))
111140
.toList();
112141
}
113142

143+
private boolean isCachedValueParameter(ParameterDefinition parameterDefinition) {
144+
if (cachedValueNames.isEmpty()) {
145+
return false;
146+
}
147+
148+
var paramName = parameterDefinition.getName();
149+
if (!cachedValueNames.contains(paramName.toUpperCase(Locale.ENGLISH))) {
150+
return false;
151+
}
152+
153+
// Check if module has a client variable with this name
154+
return hasClientModuleVariable(paramName);
155+
}
156+
157+
private boolean hasClientModuleVariable(String variableName) {
158+
return Trees.findAllRuleNodes(documentContext.getAst(), BSLParser.RULE_moduleVar).stream()
159+
.filter(BSLParser.ModuleVarContext.class::isInstance)
160+
.map(BSLParser.ModuleVarContext.class::cast)
161+
.filter(ctx -> hasVariableWithName(ctx, variableName))
162+
.anyMatch(this::hasClientCompilerDirective);
163+
}
164+
165+
private boolean hasVariableWithName(BSLParser.ModuleVarContext ctx, String variableName) {
166+
return Trees.findAllRuleNodes(ctx, BSLParser.RULE_moduleVarDeclaration).stream()
167+
.filter(BSLParser.ModuleVarDeclarationContext.class::isInstance)
168+
.map(BSLParser.ModuleVarDeclarationContext.class::cast)
169+
.anyMatch(decl -> decl.var_name().getText().equalsIgnoreCase(variableName));
170+
}
171+
172+
private boolean hasClientCompilerDirective(BSLParser.ModuleVarContext ctx) {
173+
return ctx.compilerDirective().stream()
174+
.map(BSLParser.CompilerDirectiveContext::getStop)
175+
.map(Token::getType)
176+
.map(CompilerDirectiveKind::of)
177+
.flatMap(Optional::stream)
178+
.anyMatch(directive -> directive == CompilerDirectiveKind.AT_CLIENT);
179+
}
180+
114181
private boolean isAssignedParam(MethodSymbol method, ParameterDefinition parameterDefinition) {
115182
return getVariableByParameter(method, parameterDefinition)
116183
.noneMatch(variableSymbol -> referenceIndex.getReferencesTo(variableSymbol).stream()
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
diagnosticMessage=Set the modifier "ByValue" for the "%s" parameter of the "%s" method
22
diagnosticName=Transferring parameters between the client and the server
3+
cachedValueNames=List of parameter names to ignore (e.g., CachedValues). The diagnostic is ignored if a variable with the same name and the &AtClient compiler directive exists in the module
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
diagnosticMessage=Установите модификатор "Знач" для параметра %s метода %s
22
diagnosticName=Передача параметров между клиентом и сервером
3+
cachedValueNames=Список имен параметров для игнорирования (например, КэшированныеЗначения). Диагностика игнорируется, если в модуле существует переменная с таким же именем и директивой компиляции &НаКлиенте

src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/TransferringParametersBetweenClientAndServerDiagnosticTest.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
*/
2222
package com.github._1c_syntax.bsl.languageserver.diagnostics;
2323

24+
import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;
2425
import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterClass;
26+
import com.github._1c_syntax.bsl.languageserver.util.TestUtils;
2527
import org.eclipse.lsp4j.Diagnostic;
2628
import org.junit.jupiter.api.Test;
2729

2830
import java.util.List;
31+
import java.util.Map;
2932

3033
import static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat;
3134

@@ -45,6 +48,115 @@ void test() {
4548
.hasSize(1);
4649
}
4750

51+
@Test
52+
void testWithCachedValues() {
53+
var code = """
54+
&НаКлиенте
55+
Перем КэшированныеЗначения;
56+
57+
&НаКлиенте
58+
Процедура Клиент1()
59+
Сервер1(2);
60+
КонецПроцедуры
61+
62+
&НаСервере
63+
Процедура Сервер1(КэшированныеЗначения) // не ошибка - есть переменная &НаКлиенте
64+
Метод(КэшированныеЗначения);
65+
КонецПроцедуры
66+
67+
&НаКлиенте
68+
Процедура Клиент2()
69+
Сервер2(2);
70+
КонецПроцедуры
71+
72+
&НаСервере
73+
Процедура Сервер2(ДругойПарам) // ошибка - нет переменной с таким именем
74+
Метод(ДругойПарам);
75+
КонецПроцедуры
76+
77+
&НаСервере
78+
Процедура Метод(Парам)
79+
КонецПроцедуры
80+
""";
81+
82+
var documentContext = TestUtils.getDocumentContext(code);
83+
84+
Map<String, Object> configuration = diagnosticInstance.getInfo().getDefaultConfiguration();
85+
configuration.put("cachedValueNames", "КэшированныеЗначения");
86+
diagnosticInstance.configure(configuration);
87+
88+
var diagnostics = getDiagnostics(documentContext);
89+
90+
assertThat(diagnostics, true)
91+
.hasMessageOnRange(getMessage("ДругойПарам", "Сервер2"), 19, 18, 29)
92+
.hasSize(1);
93+
}
94+
95+
@Test
96+
void testWithCachedValuesButNoVariable() {
97+
var code = """
98+
&НаКлиенте
99+
Процедура Клиент1()
100+
Сервер1(2);
101+
КонецПроцедуры
102+
103+
&НаСервере
104+
Процедура Сервер1(КэшированныеЗначения) // ошибка - нет переменной &НаКлиенте
105+
Метод(КэшированныеЗначения);
106+
КонецПроцедуры
107+
108+
&НаСервере
109+
Процедура Метод(Парам)
110+
КонецПроцедуры
111+
""";
112+
113+
var documentContext = TestUtils.getDocumentContext(code);
114+
115+
Map<String, Object> configuration = diagnosticInstance.getInfo().getDefaultConfiguration();
116+
configuration.put("cachedValueNames", "КэшированныеЗначения");
117+
diagnosticInstance.configure(configuration);
118+
119+
var diagnostics = getDiagnostics(documentContext);
120+
121+
assertThat(diagnostics, true)
122+
.hasMessageOnRange(getMessage("КэшированныеЗначения", "Сервер1"), 6, 18, 38)
123+
.hasSize(1);
124+
}
125+
126+
@Test
127+
void testWithCachedValuesButNotClientVariable() {
128+
var code = """
129+
&НаСервере
130+
Перем КэшированныеЗначения;
131+
132+
&НаКлиенте
133+
Процедура Клиент1()
134+
Сервер1(2);
135+
КонецПроцедуры
136+
137+
&НаСервере
138+
Процедура Сервер1(КэшированныеЗначения) // ошибка - переменная не &НаКлиенте
139+
Метод(КэшированныеЗначения);
140+
КонецПроцедуры
141+
142+
&НаСервере
143+
Процедура Метод(Парам)
144+
КонецПроцедуры
145+
""";
146+
147+
var documentContext = TestUtils.getDocumentContext(code);
148+
149+
Map<String, Object> configuration = diagnosticInstance.getInfo().getDefaultConfiguration();
150+
configuration.put("cachedValueNames", "КэшированныеЗначения");
151+
diagnosticInstance.configure(configuration);
152+
153+
var diagnostics = getDiagnostics(documentContext);
154+
155+
assertThat(diagnostics, true)
156+
.hasMessageOnRange(getMessage("КэшированныеЗначения", "Сервер1"), 9, 18, 38)
157+
.hasSize(1);
158+
}
159+
48160
private String getMessage(String paramName, String methodName) {
49161
return String.format("Установите модификатор \"Знач\" для параметра %s метода %s",
50162
paramName, methodName);

0 commit comments

Comments
 (0)