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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/diagnostics/TransferringParametersBetweenClientAndServer.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,48 @@
КонецФункции
```

## Параметры

### cachedValueNames

Тип: `Строка`
Значение по умолчанию: `` (пустая строка)

Список имен параметров, разделенных запятыми, которые должны игнорироваться диагностикой, если в модуле существует переменная с таким же именем и директивой компиляции `&НаКлиенте`.

Это полезно для кэшируемых значений, которые специально передаются с сервера на клиент для хранения в переменных модуля формы.

Пример:

```json
{
"TransferringParametersBetweenClientAndServer": {
"cachedValueNames": "КэшированныеЗначения,КэшДанных"
}
}
```

Если в коде есть объявление:

```bsl
&НаКлиенте
Перем КэшированныеЗначения; // используется механизмом обработки изменения реквизитов ТЧ
```

То следующий код не будет генерировать замечание:

```bsl
&НаКлиенте
Процедура ПриИзмененииРеквизита()
ОбновитьКэш(КэшированныеЗначения);
КонецПроцедуры

&НаСервере
Процедура ОбновитьКэш(КэшированныеЗначения)
КэшированныеЗначения = ПолучитьДанныеНаСервере();
КонецПроцедуры
```

## Источники
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,49 @@ Return MessageToUser;

EndFunction
```

## Parameters

### cachedValueNames

Type: `String`
Default value: `` (empty string)

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.

This is useful for cached values that are intentionally transferred from the server to the client for storage in form module variables.

Example:

```json
{
"TransferringParametersBetweenClientAndServer": {
"cachedValueNames": "CachedValues,DataCache"
}
}
```

If there is a declaration in the code:

```bsl
&AtClient
Var CachedValues; // used by the tabular section attribute change processing mechanism
```

Then the following code will not generate a remark:

```bsl
&AtClient
Procedure OnAttributeChange()
UpdateCache(CachedValues);
EndProcedure

&AtServer
Procedure UpdateCache(CachedValues)
CachedValues = GetDataOnServer();
EndProcedure
```

## Sources
<!-- It is necessary to provide links to all sources from which information was obtained to create diagnostics -->
<!-- Sample sources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,29 @@
import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol;
import com.github._1c_syntax.bsl.languageserver.context.symbol.annotations.CompilerDirectiveKind;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticParameter;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType;
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex;
import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType;
import com.github._1c_syntax.bsl.languageserver.references.model.Reference;
import com.github._1c_syntax.bsl.languageserver.utils.RelatedInformation;
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
import com.github._1c_syntax.bsl.parser.BSLParser;
import lombok.RequiredArgsConstructor;
import org.antlr.v4.runtime.Token;
import org.eclipse.lsp4j.DiagnosticRelatedInformation;
import org.eclipse.lsp4j.SymbolKind;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -62,9 +70,29 @@
CompilerDirectiveKind.AT_SERVER,
CompilerDirectiveKind.AT_SERVER_NO_CONTEXT
);
private static final String DEFAULT_CACHED_VALUE_NAMES = "";

private final ReferenceIndex referenceIndex;

@DiagnosticParameter(
type = String.class,
defaultValue = DEFAULT_CACHED_VALUE_NAMES

Check warning on line 79 in src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/TransferringParametersBetweenClientAndServerDiagnostic.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this default value assigned to parameter "defaultValue".

See more on https://sonarcloud.io/project/issues?id=1c-syntax_bsl-language-server&issues=AZsCrMJLMI8WbQHPfojZ&open=AZsCrMJLMI8WbQHPfojZ&pullRequest=3591
)
private final Set<String> cachedValueNames = new HashSet<>();

@Override
public void configure(Map<String, Object> configuration) {
this.cachedValueNames.clear();
var cachedValueNamesString =
(String) configuration.getOrDefault("cachedValueNames", DEFAULT_CACHED_VALUE_NAMES);
if (!cachedValueNamesString.isBlank()) {
Arrays.stream(cachedValueNamesString.split(","))
.map(String::trim)
.map(name -> name.toUpperCase(Locale.ENGLISH))
.forEach(this.cachedValueNames::add);
}
}

// Не учитываются вложенные вызовы. Только прямые - клиентский метод вызывает серверный метод напрямую

@Override
Expand Down Expand Up @@ -107,10 +135,49 @@
private List<ParameterDefinition> calcNotAssignedParams(MethodSymbol method,
List<ParameterDefinition> parameterDefinitions) {
return parameterDefinitions.stream()
.filter(parameterDefinition -> !isCachedValueParameter(parameterDefinition))
.filter(parameterDefinition -> isAssignedParam(method, parameterDefinition))
.toList();
}

private boolean isCachedValueParameter(ParameterDefinition parameterDefinition) {
if (cachedValueNames.isEmpty()) {
return false;
}

var paramName = parameterDefinition.getName();
if (!cachedValueNames.contains(paramName.toUpperCase(Locale.ENGLISH))) {
return false;
}

// Check if module has a client variable with this name
return hasClientModuleVariable(paramName);
}

private boolean hasClientModuleVariable(String variableName) {
return Trees.findAllRuleNodes(documentContext.getAst(), BSLParser.RULE_moduleVar).stream()
.filter(BSLParser.ModuleVarContext.class::isInstance)
.map(BSLParser.ModuleVarContext.class::cast)
.filter(ctx -> hasVariableWithName(ctx, variableName))
.anyMatch(this::hasClientCompilerDirective);
}

private boolean hasVariableWithName(BSLParser.ModuleVarContext ctx, String variableName) {

Check warning on line 165 in src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/TransferringParametersBetweenClientAndServerDiagnostic.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make "hasVariableWithName" a "static" method.

See more on https://sonarcloud.io/project/issues?id=1c-syntax_bsl-language-server&issues=AZsCrMJLMI8WbQHPfojX&open=AZsCrMJLMI8WbQHPfojX&pullRequest=3591
return Trees.findAllRuleNodes(ctx, BSLParser.RULE_moduleVarDeclaration).stream()
.filter(BSLParser.ModuleVarDeclarationContext.class::isInstance)
.map(BSLParser.ModuleVarDeclarationContext.class::cast)
.anyMatch(decl -> decl.var_name().getText().equalsIgnoreCase(variableName));
}

private boolean hasClientCompilerDirective(BSLParser.ModuleVarContext ctx) {

Check warning on line 172 in src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/TransferringParametersBetweenClientAndServerDiagnostic.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make "hasClientCompilerDirective" a "static" method.

See more on https://sonarcloud.io/project/issues?id=1c-syntax_bsl-language-server&issues=AZsCrMJLMI8WbQHPfojY&open=AZsCrMJLMI8WbQHPfojY&pullRequest=3591
return ctx.compilerDirective().stream()
.map(BSLParser.CompilerDirectiveContext::getStop)
.map(Token::getType)
.map(CompilerDirectiveKind::of)
.flatMap(Optional::stream)
.anyMatch(directive -> directive == CompilerDirectiveKind.AT_CLIENT);
}

private boolean isAssignedParam(MethodSymbol method, ParameterDefinition parameterDefinition) {
return getVariableByParameter(method, parameterDefinition)
.noneMatch(variableSymbol -> referenceIndex.getReferencesTo(variableSymbol).stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
diagnosticMessage=Set the modifier "ByValue" for the "%s" parameter of the "%s" method
diagnosticName=Transferring parameters between the client and the server
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 number Diff line number Diff line change
@@ -1,2 +1,3 @@
diagnosticMessage=Установите модификатор "Знач" для параметра %s метода %s
diagnosticName=Передача параметров между клиентом и сервером
cachedValueNames=Список имен параметров для игнорирования (например, КэшированныеЗначения). Диагностика игнорируется, если в модуле существует переменная с таким же именем и директивой компиляции &НаКлиенте
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;

Check warning on line 24 in src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/TransferringParametersBetweenClientAndServerDiagnosticTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import 'com.github._1c_syntax.bsl.languageserver.context.DocumentContext'.

See more on https://sonarcloud.io/project/issues?id=1c-syntax_bsl-language-server&issues=AZsCrMJyMI8WbQHPfoja&open=AZsCrMJyMI8WbQHPfoja&pullRequest=3591
import com.github._1c_syntax.bsl.languageserver.util.CleanupContextBeforeClassAndAfterClass;
import com.github._1c_syntax.bsl.languageserver.util.TestUtils;
import org.eclipse.lsp4j.Diagnostic;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

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

Expand All @@ -45,6 +48,115 @@
.hasSize(1);
}

@Test
void testWithCachedValues() {
var code = """
&НаКлиенте
Перем КэшированныеЗначения;

&НаКлиенте
Процедура Клиент1()
Сервер1(2);
КонецПроцедуры

&НаСервере
Процедура Сервер1(КэшированныеЗначения) // не ошибка - есть переменная &НаКлиенте
Метод(КэшированныеЗначения);
КонецПроцедуры

&НаКлиенте
Процедура Клиент2()
Сервер2(2);
КонецПроцедуры

&НаСервере
Процедура Сервер2(ДругойПарам) // ошибка - нет переменной с таким именем
Метод(ДругойПарам);
КонецПроцедуры

&НаСервере
Процедура Метод(Парам)
КонецПроцедуры
""";

var documentContext = TestUtils.getDocumentContext(code);

Map<String, Object> configuration = diagnosticInstance.getInfo().getDefaultConfiguration();
configuration.put("cachedValueNames", "КэшированныеЗначения");
diagnosticInstance.configure(configuration);

var diagnostics = getDiagnostics(documentContext);

assertThat(diagnostics, true)
.hasMessageOnRange(getMessage("ДругойПарам", "Сервер2"), 19, 18, 29)
.hasSize(1);
}

@Test
void testWithCachedValuesButNoVariable() {
var code = """
&НаКлиенте
Процедура Клиент1()
Сервер1(2);
КонецПроцедуры

&НаСервере
Процедура Сервер1(КэшированныеЗначения) // ошибка - нет переменной &НаКлиенте
Метод(КэшированныеЗначения);
КонецПроцедуры

&НаСервере
Процедура Метод(Парам)
КонецПроцедуры
""";

var documentContext = TestUtils.getDocumentContext(code);

Map<String, Object> configuration = diagnosticInstance.getInfo().getDefaultConfiguration();
configuration.put("cachedValueNames", "КэшированныеЗначения");
diagnosticInstance.configure(configuration);

var diagnostics = getDiagnostics(documentContext);

assertThat(diagnostics, true)
.hasMessageOnRange(getMessage("КэшированныеЗначения", "Сервер1"), 6, 18, 38)
.hasSize(1);
}

@Test
void testWithCachedValuesButNotClientVariable() {
var code = """
&НаСервере
Перем КэшированныеЗначения;

&НаКлиенте
Процедура Клиент1()
Сервер1(2);
КонецПроцедуры

&НаСервере
Процедура Сервер1(КэшированныеЗначения) // ошибка - переменная не &НаКлиенте
Метод(КэшированныеЗначения);
КонецПроцедуры

&НаСервере
Процедура Метод(Парам)
КонецПроцедуры
""";

var documentContext = TestUtils.getDocumentContext(code);

Map<String, Object> configuration = diagnosticInstance.getInfo().getDefaultConfiguration();
configuration.put("cachedValueNames", "КэшированныеЗначения");
diagnosticInstance.configure(configuration);

var diagnostics = getDiagnostics(documentContext);

assertThat(diagnostics, true)
.hasMessageOnRange(getMessage("КэшированныеЗначения", "Сервер1"), 9, 18, 38)
.hasSize(1);
}

private String getMessage(String paramName, String methodName) {
return String.format("Установите модификатор \"Знач\" для параметра %s метода %s",
paramName, methodName);
Expand Down
Loading