diff --git a/docs/diagnostics/MissingQueryParameter.md b/docs/diagnostics/MissingQueryParameter.md
new file mode 100644
index 00000000000..8e37e7ebaa1
--- /dev/null
+++ b/docs/diagnostics/MissingQueryParameter.md
@@ -0,0 +1,36 @@
+# Все параметры запроса инициализированы (MissingQueryParameter)
+
+| Тип | Поддерживаются
языки | Важность | Включена
по умолчанию | Время на
исправление (мин) | Теги |
+|:-------------:|:-----------------------------:|:----------------:|:------------------------------:|:-----------------------------------:|:------------:|
+| `Дефект кода` | `BSL`
`OS` | `Информационный` | `Да` | `1` | `suspicious` |
+
+
+## Описание диагностики
+
+
+## Примеры
+
+
+## Источники
+
+
+
+## Сниппеты
+
+
+### Экранирование кода
+
+```bsl
+// BSLLS:MissingQueryParameter-off
+// BSLLS:MissingQueryParameter-on
+```
+
+### Параметр конфигурационного файла
+
+```json
+"MissingQueryParameter": false
+```
diff --git a/docs/diagnostics/index.md b/docs/diagnostics/index.md
index 29e1606340c..d4540f539a9 100644
--- a/docs/diagnostics/index.md
+++ b/docs/diagnostics/index.md
@@ -8,12 +8,12 @@
## Список реализованных диагностик
-Общее количество: **157**
+Общее количество: **158**
* Потенциальная уязвимость: **4**
* Уязвимость: **4**
* Ошибка: **52**
-* Дефект кода: **97**
+* Дефект кода: **98**
| Ключ | Название | Включена по умолчанию | Важность | Тип | Тэги |
@@ -100,6 +100,7 @@
[MissingCodeTryCatchEx](MissingCodeTryCatchEx.md) | Конструкция "Попытка...Исключение...КонецПопытки" не содержит кода в исключении | Да | Важный | Ошибка | `standard`
`badpractice`
[MissingEventSubscriptionHandler](MissingEventSubscriptionHandler.md) | Отсутствует обработчик подписки на событие | Да | Блокирующий | Ошибка | `error`
[MissingParameterDescription](MissingParameterDescription.md) | Отсутствует описание параметров метода | Да | Важный | Дефект кода | `standard`
`badpractice`
+ [MissingQueryParameter](MissingQueryParameter.md) | Все параметры запроса инициализированы | Да | Информационный | Дефект кода | `suspicious`
[MissingReturnedValueDescription](MissingReturnedValueDescription.md) | Отсутствует описание возвращаемого значения функции | Да | Важный | Дефект кода | `standard`
`badpractice`
[MissingSpace](MissingSpace.md) | Пропущены пробелы слева или справа от операторов `+ - * / = % < > <> <= >=`, от ключевых слов, а так же справа от `,` и `;` | Да | Информационный | Дефект кода | `badpractice`
[MissingTempStorageDeletion](MissingTempStorageDeletion.md) | Отсутствует удаление данных из временного хранилища после использования | Нет | Критичный | Дефект кода | `standard`
`performance`
`badpractice`
diff --git a/docs/en/diagnostics/MissingQueryParameter.md b/docs/en/diagnostics/MissingQueryParameter.md
new file mode 100644
index 00000000000..8df9397944d
--- /dev/null
+++ b/docs/en/diagnostics/MissingQueryParameter.md
@@ -0,0 +1,36 @@
+# Все параметры запроса инициализированы (MissingQueryParameter)
+
+| Type | Scope | Severity | Activated
by default | Minutes
to fix | Tags |
+|:------------:|:-------------------:|:--------:|:-----------------------------:|:-----------------------:|:------------:|
+| `Code smell` | `BSL`
`OS` | `Info` | `Yes` | `1` | `suspicious` |
+
+
+## Description
+
+
+## Examples
+
+
+## Sources
+
+
+
+## Snippets
+
+
+### Diagnostic ignorance in code
+
+```bsl
+// BSLLS:MissingQueryParameter-off
+// BSLLS:MissingQueryParameter-on
+```
+
+### Parameter for config
+
+```json
+"MissingQueryParameter": false
+```
diff --git a/docs/en/diagnostics/index.md b/docs/en/diagnostics/index.md
index be4f569d67a..a0758df2543 100644
--- a/docs/en/diagnostics/index.md
+++ b/docs/en/diagnostics/index.md
@@ -8,12 +8,12 @@ To escape individual sections of code or files from triggering diagnostics, you
## Implemented diagnostics
-Total: **157**
+Total: **158**
* Security Hotspot: **4**
* Vulnerability: **4**
* Error: **52**
-* Code smell: **97**
+* Code smell: **98**
| Key | Name| Enabled by default | Severity | Type | Tags |
@@ -100,6 +100,7 @@ Total: **157**
[MissingCodeTryCatchEx](MissingCodeTryCatchEx.md) | Missing code in Raise block in "Try ... Raise ... EndTry" | Yes | Major | Error | `standard`
`badpractice`
[MissingEventSubscriptionHandler](MissingEventSubscriptionHandler.md) | Event subscription handler missing | Yes | Blocker | Error | `error`
[MissingParameterDescription](MissingParameterDescription.md) | Method parameters description are missing | Yes | Major | Code smell | `standard`
`badpractice`
+ [MissingQueryParameter](MissingQueryParameter.md) | Все параметры запроса инициализированы | Yes | Info | Code smell | `suspicious`
[MissingReturnedValueDescription](MissingReturnedValueDescription.md) | Function returned values description is missing | Yes | Major | Code smell | `standard`
`badpractice`
[MissingSpace](MissingSpace.md) | Missing spaces to the left or right of operators + - * / = % < > <> <= >=, keywords, and also to the right of , and ; | Yes | Info | Code smell | `badpractice`
[MissingTempStorageDeletion](MissingTempStorageDeletion.md) | Missing temporary storage data deletion after using | No | Critical | Code smell | `standard`
`performance`
`badpractice`
diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnostic.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnostic.java
new file mode 100644
index 00000000000..77ef844a97d
--- /dev/null
+++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnostic.java
@@ -0,0 +1,331 @@
+/*
+ * This file is a part of BSL Language Server.
+ *
+ * Copyright (c) 2018-2021
+ * Alexey Sosnoviy , Nikita Gryzlov 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.diagnostics;
+
+import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
+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.utils.Ranges;
+import com.github._1c_syntax.bsl.languageserver.utils.Trees;
+import com.github._1c_syntax.bsl.parser.BSLParser;
+import com.github._1c_syntax.bsl.parser.BSLParser.AssignmentContext;
+import com.github._1c_syntax.bsl.parser.BSLParser.CodeBlockContext;
+import com.github._1c_syntax.bsl.parser.BSLParserRuleContext;
+import com.github._1c_syntax.bsl.parser.SDBLParser;
+import com.github._1c_syntax.bsl.parser.SDBLParser.ParameterContext;
+import com.github._1c_syntax.bsl.parser.SDBLParser.QueryPackageContext;
+import com.github._1c_syntax.bsl.parser.Tokenizer;
+import com.github._1c_syntax.utils.CaseInsensitivePattern;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+@DiagnosticMetadata(
+ type = DiagnosticType.CODE_SMELL,
+ severity = DiagnosticSeverity.INFO,
+ minutesToFix = 1,
+ tags = {
+ DiagnosticTag.SUSPICIOUS
+ }
+
+)
+public class MissingQueryParameterDiagnostic extends AbstractVisitorDiagnostic {
+
+ private static final Pattern QUERY_PATTERN = CaseInsensitivePattern.compile(
+ "Запрос|Query");
+ private static final Pattern QUERY_TEXT_PATTERN = CaseInsensitivePattern.compile(
+ "Текст|Text");
+ private static final Pattern SET_PARAMETER_PATTERN = CaseInsensitivePattern.compile(
+ "УстановитьПараметр|SetParameter");
+ public static final int SET_PARAMETER_PARAMS_COUNT = 2;
+
+ private Collection codeBlocks = Collections.emptyList();
+ private final Map> codeBlockAssignments = new HashMap<>();
+ private final Map> codeBlockCallStatements = new HashMap<>();
+
+ enum QueryVarKind {
+ NEW_QUERY,
+ QUERY_TEXT,
+ EMPTY,
+ VAR
+ }
+
+ @AllArgsConstructor
+ @Getter
+ private static class QueryTextSetupData {
+ private QueryVarKind kind;
+ private String varName;
+ private BSLParser.ExpressionContext rightExpr;
+ private Optional newQueryExpr;
+
+ boolean isQueryObject(){
+ return kind == QueryVarKind.NEW_QUERY || kind == QueryVarKind.QUERY_TEXT;
+ }
+ boolean isVar(){
+ return kind == QueryVarKind.VAR;
+ }
+ }
+
+ @Override
+ public ParseTree visitFile(BSLParser.FileContext file) {
+
+ final var queriesWithParams = documentContext.getQueries().stream()
+ .map(Tokenizer::getAst)
+ .map(ctx -> Pair.of(ctx, getParams(ctx)))
+ .collect(Collectors.toList());
+
+ if (!queriesWithParams.isEmpty()) {
+ codeBlocks = getCodeBlocks();
+
+ queriesWithParams.forEach(query -> visitQuery(query.getLeft(), query.getRight()));
+
+ codeBlocks.clear();
+ codeBlockAssignments.clear();
+ codeBlockCallStatements.clear();
+
+ }
+
+ return super.visitFile(file);
+ }
+
+ private static Map getParams(QueryPackageContext queryPackage) {
+ return Trees.findAllRuleNodes(queryPackage, SDBLParser.RULE_parameter).stream()
+ .filter(ParameterContext.class::isInstance)
+ .map(ParameterContext.class::cast)
+// .map(ctx -> Pair.of(ctx, "\"" + ctx.name.getText() + "\""))
+ // если есть несколько одинаковых параметров в запросе
+ .collect(Collectors.toMap(ctx -> "\"" + ctx.name.getText() + "\"", ctx -> ctx,
+ (parameterContext, parameterContext2) -> parameterContext));
+// .collect(Collectors.toSet());
+ }
+
+ private Collection getCodeBlocks() {
+ final var ast = documentContext.getAst();
+ var blocks = getSubBlocks(ast);
+ final var fileCodeBlock = Optional.ofNullable(ast.fileCodeBlock()).map(BSLParser.FileCodeBlockContext::codeBlock);
+ fileCodeBlock.ifPresent(blocks::add);
+ final var fileCodeBlockBeforeSub =
+ Optional.ofNullable(ast.fileCodeBlockBeforeSub())
+ .map(BSLParser.FileCodeBlockBeforeSubContext::codeBlock);
+ fileCodeBlockBeforeSub.ifPresent(blocks::add);
+ return blocks;
+ }
+
+ private static Collection getSubBlocks(BSLParser.FileContext ast) {
+ if (ast.subs() == null) {
+ return new ArrayList<>();
+ }
+ return ast.subs().sub().stream().map((BSLParser.SubContext sub) -> {
+ if (sub.procedure() == null) {
+ return sub.function().subCodeBlock().codeBlock();
+ } else {
+ return sub.procedure().subCodeBlock().codeBlock();
+ }
+ }).collect(Collectors.toList());
+ }
+
+ private void visitQuery(QueryPackageContext queryPackage, Map params) {
+ final var codeBlockByQuery = getCodeBlockByQuery(queryPackage);
+ codeBlockByQuery.ifPresent(codeBlock -> getQueryTextAssignmentsInsideBlock(codeBlock, queryPackage)
+ .forEach(queryTextAssignment -> checkAssignment(codeBlock, params, queryTextAssignment)));
+ }
+
+ private Optional getCodeBlockByQuery(QueryPackageContext key) {
+ return codeBlocks.stream()
+ .filter(block -> Ranges.containsRange(Ranges.create(block), Ranges.create(key)))
+ .findFirst();
+ }
+
+ private List getQueryTextAssignmentsInsideBlock(CodeBlockContext codeBlock,
+ QueryPackageContext queryPackage) {
+
+ final var queryAssignments = codeBlockAssignments.computeIfAbsent(codeBlock,
+ MissingQueryParameterDiagnostic::getAllQueryAssignmentInsideBlock);
+
+ final var queryObjectTextAssignments = queryAssignments.stream()
+ .filter(QueryTextSetupData::isQueryObject)
+ .collect(Collectors.toList());
+
+ final var queryTextAssignments = queryAssignments.stream()
+ .filter(QueryTextSetupData::isVar)
+ .flatMap(queryTextSetupData -> getQueryTextAssignment(queryObjectTextAssignments,
+ queryTextSetupData.getRightExpr(), queryTextSetupData.getVarName())
+ .stream())
+ .collect(Collectors.toList());
+
+ final var queryRange = Ranges.create(queryPackage);
+ final var defaultQueryTextAssignments = queryObjectTextAssignments.stream()
+ .filter(queryTextSetupData -> Ranges.containsRange(Ranges.create(queryTextSetupData.getRightExpr()), queryRange))
+ .collect(Collectors.toList());
+
+ queryTextAssignments.addAll(defaultQueryTextAssignments);
+ return queryTextAssignments;
+ }
+
+ private static List getAllQueryAssignmentInsideBlock(CodeBlockContext codeBlock) {
+ return Trees.findAllRuleNodes(codeBlock, BSLParser.RULE_assignment).stream()
+ .filter(AssignmentContext.class::isInstance)
+ .map(AssignmentContext.class::cast)
+ .map(MissingQueryParameterDiagnostic::computeQueryVarData)
+ .filter(queryTextSetupData -> queryTextSetupData.getKind() != QueryVarKind.EMPTY)
+ .collect(Collectors.toList());
+ }
+
+ private static QueryTextSetupData computeQueryVarData(AssignmentContext assignment) {
+ final var newQueryExpr = isNewQueryExpr(assignment);
+ if (newQueryExpr.isPresent()) {
+ return new QueryTextSetupData(QueryVarKind.NEW_QUERY, assignment.lValue().getText(), assignment.expression(),
+ newQueryExpr);
+ }
+ final var pair = computeQueryVarNameFromLValue(assignment.lValue());
+ return new QueryTextSetupData(pair.getRight(), pair.getLeft(), assignment.expression(), Optional.empty());
+ }
+
+ private static Optional isNewQueryExpr(AssignmentContext assignment) {
+ return Trees.findAllRuleNodes(assignment, BSLParser.RULE_newExpression).stream()
+ .filter(BSLParser.NewExpressionContext.class::isInstance)
+ .map(BSLParser.NewExpressionContext.class::cast)
+ .filter(ctx -> ctx.typeName() != null)
+ .filter(ctx -> QUERY_PATTERN.matcher(ctx.typeName().getText()).matches())
+ .map(BSLParser.NewExpressionContext::doCall)
+ .filter(Objects::nonNull)
+ .map(BSLParser.DoCallContext::callParamList)
+ .filter(Objects::nonNull)
+ .findFirst();
+ }
+
+ private static Pair computeQueryVarNameFromLValue(BSLParser.LValueContext lValue) {
+ final var identifier = lValue.IDENTIFIER();
+ final var acceptor = lValue.acceptor();
+ if (acceptor == null || identifier == null) {
+ return Pair.of(lValue.getText(), QueryVarKind.VAR);
+ }
+ return Optional.of(acceptor)
+ .map(BSLParser.AcceptorContext::accessProperty)
+ .map(BSLParser.AccessPropertyContext::IDENTIFIER)
+ .map(ParseTree::getText)
+ .filter(s -> QUERY_TEXT_PATTERN.matcher(s).matches())
+ .map(s -> identifier.getText())
+ .map(s -> Pair.of(s, QueryVarKind.QUERY_TEXT))
+ .orElse(Pair.of("", QueryVarKind.EMPTY));
+ }
+
+ private Optional getQueryTextAssignment(List queryObjectTextAssignments,
+ BSLParser.ExpressionContext varAssign, String varName) {
+ return queryObjectTextAssignments.stream()
+ .filter(queryTextSetupData -> queryTextSetupData.getRightExpr().getStop().getLine() > varAssign.getStop().getLine())
+ .filter(queryTextSetupData -> queryTextSetupData.getNewQueryExpr()
+ .filter(callParamListContext -> callParamListContext.getText().equalsIgnoreCase(varName))
+ .isPresent())
+ .findFirst()
+ ;
+ }
+
+ private void checkAssignment(CodeBlockContext codeBlock,
+ Map params,
+ QueryTextSetupData queryTextAssignment) {
+
+ final var callStatements = codeBlockCallStatements.computeIfAbsent(codeBlock,
+ MissingQueryParameterDiagnostic::getIsSetParameterCallStatements);
+
+ final var allParams = params.values();
+
+ if (!callStatements.isEmpty()) {
+
+ final var queryVarName = queryTextAssignment.getVarName();
+ final var usedParams = callStatements.stream()
+ .map(callStatementContext -> findAppropriateParamFromSetParameter(callStatementContext, queryVarName, params))
+ .flatMap(Optional::stream)
+ .collect(Collectors.toList());
+ allParams.removeAll(usedParams);
+ }
+
+ allParams.forEach(node -> diagnosticStorage.addDiagnostic(node,
+ info.getMessage(node.PARAMETER_IDENTIFIER().getText())));
+ }
+
+ private static List getIsSetParameterCallStatements(CodeBlockContext codeBlock) {
+ return Trees.findAllRuleNodes(codeBlock, BSLParser.RULE_callStatement).stream()
+ .filter(BSLParser.CallStatementContext.class::isInstance)
+ .map(BSLParser.CallStatementContext.class::cast)
+ .filter(MissingQueryParameterDiagnostic::isSetParameterCall)
+ .collect(Collectors.toList());
+ }
+
+ private static boolean isSetParameterCall(BSLParser.CallStatementContext callStatement) {
+ return Optional.of(callStatement)
+ .map(BSLParser.CallStatementContext::accessCall)
+ .map(BSLParser.AccessCallContext::methodCall)
+ .map(BSLParser.MethodCallContext::methodName)
+ .filter(ctx -> SET_PARAMETER_PATTERN.matcher(ctx.getText()).matches())
+ .isPresent();
+ }
+
+ private static Optional findAppropriateParamFromSetParameter(BSLParser.CallStatementContext callStatementContext,
+ String queryVarName,
+ Map params) {
+ final var callCtx = Optional.of(callStatementContext);
+ return callCtx
+ .map(BSLParser.CallStatementContext::IDENTIFIER)
+ .map(ParseTree::getText)
+ .filter(queryVarName::equalsIgnoreCase)
+ .flatMap(dummy -> findAppropriateParamFromSetParameterMethod(callCtx, params));
+ }
+
+ private static Optional findAppropriateParamFromSetParameterMethod(Optional callCtx,
+ Map params) {
+ return callCtx.map(BSLParser.CallStatementContext::accessCall)
+ .map(BSLParser.AccessCallContext::methodCall)
+ .map(BSLParser.MethodCallContext::doCall)
+ .map(BSLParser.DoCallContext::callParamList)
+ .map(BSLParser.CallParamListContext::callParam)
+ .filter(callParamContexts -> callParamContexts.size() == SET_PARAMETER_PARAMS_COUNT)
+ .map(callParamContexts -> callParamContexts.get(0))
+ .map(BSLParser.CallParamContext::expression)
+ .map(BSLParser.ExpressionContext::member)
+ .filter(memberContexts -> !memberContexts.isEmpty())
+ .map(memberContexts -> memberContexts.get(0))
+ .map(BSLParser.MemberContext::constValue)
+ .map(BSLParserRuleContext::getText)
+ .flatMap(firstValueForSetParameterMethod -> findParameterByName(params, firstValueForSetParameterMethod));
+ }
+
+ private static Optional findParameterByName(Map params,
+ String firstValueForSetParameterMethod) {
+ return params.entrySet().stream()
+ .filter(entry -> entry.getKey().equalsIgnoreCase(firstValueForSetParameterMethod))
+ .map(Map.Entry::getValue).findFirst();
+ }
+}
diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json
index 2935ffe44cf..4835bcfb555 100644
--- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json
+++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json
@@ -1078,6 +1078,16 @@
"title": "Method parameters description are missing",
"$id": "#/definitions/MissingParameterDescription"
},
+ "MissingQueryParameter": {
+ "description": "\u0412\u0441\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u044b",
+ "default": true,
+ "type": [
+ "boolean",
+ "object"
+ ],
+ "title": "\u0412\u0441\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u044b",
+ "$id": "#/definitions/MissingQueryParameter"
+ },
"MissingReturnedValueDescription": {
"description": "Function returned values description is missing",
"default": true,
diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnostic_ru.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnostic_ru.properties
new file mode 100644
index 00000000000..245722c9b8e
--- /dev/null
+++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnostic_ru.properties
@@ -0,0 +1,2 @@
+diagnosticMessage=Добавьте установку параметра запроса "%s"
+diagnosticName=Все параметры запроса инициализированы
diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnosticTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnosticTest.java
new file mode 100644
index 00000000000..a4e90105bd6
--- /dev/null
+++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/MissingQueryParameterDiagnosticTest.java
@@ -0,0 +1,248 @@
+/*
+ * This file is a part of BSL Language Server.
+ *
+ * Copyright (c) 2018-2021
+ * Alexey Sosnoviy , Nikita Gryzlov 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.diagnostics;
+
+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 static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat;
+
+class MissingQueryParameterDiagnosticTest extends AbstractDiagnosticTest {
+ MissingQueryParameterDiagnosticTest() {
+ super(MissingQueryParameterDiagnostic.class);
+ }
+
+ @Test
+ void test() {
+
+ List diagnostics = getDiagnostics();
+
+ assertThat(diagnostics, true)
+// .hasMessageOnRange("Добавьте установку параметра запроса \"Параметр1\"", 7, 34, 44)
+// .hasMessageOnRange("Добавьте установку параметра запроса \"Параметр3\"", 35, 34, 44)
+// .hasMessageOnRange("Добавьте установку параметра запроса \"Параметр5\"", 63, 34, 44)
+ .hasSize(3)
+ ;
+
+ }
+
+ @Test
+ void testNewQuery() {
+ var sample =
+ " Запрос1 = Новый Запрос(\n" +
+ " \"ВЫБРАТЬ\n" +
+ " | Справочник1.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник1 КАК Справочник1\n" +
+ " |ГДЕ\n" +
+ " | Справочник1.Ссылка = &Параметр\");\n" +
+ "\n" +
+ " РезультатЗапроса = Запрос1.Выполнить();\n";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).hasSize(1);
+ }
+
+ @Test
+ void testNewQueryNoError() {
+ var sample =
+ " Запрос2 = Новый Запрос(\n" +
+ " \"ВЫБРАТЬ\n" +
+ " | Справочник2.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник2 КАК Справочник2\n" +
+ " |ГДЕ\n" +
+ " | Справочник2.Ссылка = &Параметр2\");\n" +
+ "Запрос2.УстановитьПараметр(\"Параметр2\", Ссылка);\n" +
+ " РезультатЗапроса = Запрос2.Выполнить();\n";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).isEmpty();
+ }
+
+ @Test
+ void testQueryTextNoError() {
+ var sample =
+ "Процедура Четвертая_НетОшибки_ЗапросТекст()\n" +
+ "\n" +
+ " Запрос4 = Новый Запрос;\n" +
+ " Запрос4.Текст = \"ВЫБРАТЬ\n" +
+ " | Справочник4.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник4 КАК Справочник4\n" +
+ " |ГДЕ\n" +
+ " | Справочник4.Ссылка = &Параметр4\"; // нет ошибки\n" +
+ "\n" +
+ " Запрос4.УстановитьПараметр(\"Параметр4\", Ссылка);\n" +
+ "\n" +
+ " РезультатЗапроса = Запрос4.Выполнить();\n" +
+ "КонецПроцедуры\n";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).isEmpty();
+ }
+
+ @Test
+ void testQueryTextWithoutNewQuery() {
+ var sample =
+ "Процедура Шестой_ТекстБезСозданияЗапроса()\n" +
+ " НеиспользуемыйТекст6 = \"ВЫБРАТЬ\n" +
+ " | Справочник6.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник6 КАК Справочник6\n" +
+ " |ГДЕ\n" +
+ " | Справочник6.Ссылка = &Параметр6\"; // не ошибка\n" +
+// "\n" +
+// " Запрос6 = Новый Запрос(Текст);\n" +
+// " РезультатЗапроса = Запрос6.Выполнить();\n" +
+// "\n" +
+ "КонецПроцедуры\n";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).isEmpty();
+ }
+
+ @Test
+ void testQueryTextWithNewQueryForOtherText() {
+ var sample =
+ "Процедура Седьмой_СначалаТекстПотомЗапросСДругимТекстом(Текст)\n" +
+ " НеиспользуемыйТекст7 = \"ВЫБРАТЬ\n" +
+ " | Справочник7.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник7 КАК Справочник7\n" +
+ " |ГДЕ\n" +
+ " | Справочник7.Ссылка = &Параметр7\"; // не ошибка\n" +
+ " Запрос7 = Новый Запрос(Текст);\n" +
+// " //РезультатЗапроса = Запрос7.Выполнить();\n" +
+ "КонецПроцедуры\n";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).isEmpty();
+ }
+
+ @Test
+ void testQueryTextThenNewQuery() {
+ var sample =
+ "Процедура ПятаяОшибка_СначалаТекстПотомНовыйЗапрос()\n" +
+ "\n" +
+ " Текст5 = \"ВЫБРАТЬ\n" +
+ " | Справочник1.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник1 КАК Справочник1\n" +
+ " |ГДЕ\n" +
+ " | Справочник1.Ссылка = &Параметр5\"; // ошибка\n" +
+ " Запрос5 = Новый Запрос(Текст5);\n" +
+// " РезультатЗапроса = Запрос5.Выполнить();\n" +
+ "КонецПроцедуры\n";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).hasSize(1);
+ }
+
+ @Test
+ void testSetParamBeforeQueryText() {
+ var sample =
+ "Процедура Восьмой_СначалаНовыйЗапросДалееУстановкаПараметраЗатемТекстЗапросаСПовторениемПараметра(Ссылка)\n" +
+ " Запрос = Новый Запрос;\n" +
+ " Запрос.УстановитьПараметр(\"Параметр\", Ссылка);\n" +
+ " Запрос.Текст = \"ВЫБРАТЬ\n" +
+ " | Справочник1.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник1 КАК Справочник1\n" +
+ " |ГДЕ\n" +
+ " | Справочник1.Ссылка = &Параметр\n" +
+ " |\"; // нет ошибки\n" +
+// " //РезультатЗапроса = Запрос7.Выполнить();\n" +
+ "КонецПроцедуры\n";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).isEmpty();
+ }
+
+ @Test
+ void testSetParamBeforeQueryTextWithFewParams() {
+ var sample =
+ "Процедура Девятый_СначалаНовыйЗапросДалееУстановкаПараметраЗатемТекстЗапросаСПовторениемПараметра(Ссылка)\n" +
+ "\n" +
+ " Запрос = Новый Запрос;\n" +
+ " Запрос.УстановитьПараметр(\"Параметр\", Ссылка);\n" +
+ " Запрос.Текст = \"ВЫБРАТЬ\n" +
+ " | Справочник1.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник1 КАК Справочник1\n" +
+ " |ГДЕ\n" +
+ " | Справочник1.Ссылка = &Параметр\n" +
+ " |\n" +
+ " |ОБЪЕДИНИТЬ ВСЕ\n" +
+ " |\n" +
+ " |ВЫБРАТЬ\n" +
+ " | Справочник1.Ссылка КАК Ссылка\n" +
+ " |ИЗ\n" +
+ " | Справочник.Справочник1 КАК Справочник1\n" +
+ " |ГДЕ\n" +
+ " | Справочник1.Ссылка = &Параметр\n" +
+ " |\"; // нет ошибки\n" +
+ "\n" +
+// " //РезультатЗапроса = Запрос7.Выполнить();\n" +
+ "\n" +
+ "КонецПроцедуры\n";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).isEmpty();
+ }
+
+ @Test
+ void testStrReplaceForQueryText() {
+ var sample =
+ "Процедура Десятый_СначалаНовыйЗапросДалееУстановкаПараметраЗатемТекстЗапросаСПовторениемПараметра(Ссылка)\n" +
+ "\n" +
+ " ТекстЗапроса = \"ВЫБРАТЬ * ИЗ &ИмяТаблицы ГДЕ ЛОЖЬ\"; // не ошибка\n" +
+ " ТекстЗапроса = СтрЗаменить(ТекстЗапроса, \"&ИмяТаблицы\", ИсточникДанных);\n" +
+ " Запрос = Новый Запрос(ТекстЗапроса);\n" +
+// " //РезультатЗапроса = Запрос7.Выполнить();\n" +
+ "КонецПроцедуры";
+
+ var documentContext = TestUtils.getDocumentContext(sample);
+ var diagnostics = getDiagnostics(documentContext);
+
+ assertThat(diagnostics).isEmpty();
+ }
+}
diff --git a/src/test/resources/diagnostics/MissingQueryParameterDiagnostic.bsl b/src/test/resources/diagnostics/MissingQueryParameterDiagnostic.bsl
new file mode 100644
index 00000000000..97be97bb4dd
--- /dev/null
+++ b/src/test/resources/diagnostics/MissingQueryParameterDiagnostic.bsl
@@ -0,0 +1,145 @@
+Процедура ПерваяОшибка_НовыйЗапрос()
+
+ Запрос1 = Новый Запрос("ВЫБРАТЬ
+ | Справочник1.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник1 КАК Справочник1
+ |ГДЕ
+ | Справочник1.Ссылка = &Параметр1"); // ошибка
+
+ //РезультатЗапроса = Запрос1.Выполнить(); // важно создание запроса, выполнение не важно
+
+КонецПроцедуры
+
+Процедура Вторая_НетОшибки_НовыйЗапрос()
+
+ Запрос2 = Новый Запрос("ВЫБРАТЬ
+ | Справочник2.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник2 КАК Справочник2
+ |ГДЕ
+ | Справочник2.Ссылка = &Параметр2"); // нет ошибки
+
+ Запрос2.УстановитьПараметр("Параметр2", Ссылка);
+
+ //РезультатЗапроса = Запрос2.Выполнить();
+КонецПроцедуры
+
+Процедура ТретьяОшибка_ЗапросТекст()
+
+ Запрос3 = Новый Запрос;
+ Запрос3.Текст = "ВЫБРАТЬ
+ | Справочник1.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник1 КАК Справочник1
+ |ГДЕ
+ | Справочник1.Ссылка = &Параметр3"; // ошибка
+
+ //РезультатЗапроса = Запрос3.Выполнить();
+
+КонецПроцедуры
+
+Процедура Четвертая_НетОшибки_ЗапросТекст()
+
+ Запрос4 = Новый Запрос;
+ Запрос4.Текст = "ВЫБРАТЬ
+ | Справочник4.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник4 КАК Справочник4
+ |ГДЕ
+ | Справочник4.Ссылка = &Параметр4"; // нет ошибки
+
+ Запрос4.УстановитьПараметр("Параметр4", Ссылка);
+
+ //РезультатЗапроса = Запрос4.Выполнить();
+КонецПроцедуры
+
+Процедура ПятаяОшибка_СначалаТекстПотомНовыйЗапрос()
+
+ Текст5 = "ВЫБРАТЬ
+ | Справочник1.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник1 КАК Справочник1
+ |ГДЕ
+ | Справочник1.Ссылка = &Параметр5"; // ошибка
+
+ Запрос5 = Новый Запрос(Текст5);
+ //РезультатЗапроса = Запрос5.Выполнить();
+
+КонецПроцедуры
+
+Процедура Шестой_ТекстБезСозданияЗапроса()
+
+ НеиспользуемыйТекст6 = "ВЫБРАТЬ
+ | Справочник6.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник6 КАК Справочник6
+ |ГДЕ
+ | Справочник6.Ссылка = &Параметр6"; // не ошибка
+
+КонецПроцедуры
+
+Процедура Седьмой_СначалаТекстПотомЗапросСДругимТекстом(Текст)
+
+ НеиспользуемыйТекст7 = "ВЫБРАТЬ
+ | Справочник7.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник7 КАК Справочник7
+ |ГДЕ
+ | Справочник7.Ссылка = &Параметр7"; // не ошибка
+
+ Запрос7 = Новый Запрос(Текст);
+ //РезультатЗапроса = Запрос7.Выполнить();
+
+КонецПроцедуры
+
+Процедура Восьмой_СначалаНовыйЗапросДалееУстановкаПараметраЗатемТекстЗапросаСПовторениемПараметра(Ссылка)
+
+ Запрос = Новый Запрос;
+ Запрос.УстановитьПараметр("Параметр", Ссылка);
+ Запрос.Текст = "ВЫБРАТЬ
+ | Справочник1.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник1 КАК Справочник1
+ |ГДЕ
+ | Справочник1.Ссылка = &Параметр
+ |"; // нет ошибки
+
+ //РезультатЗапроса = Запрос7.Выполнить();
+
+КонецПроцедуры
+
+Процедура Девятый_СначалаНовыйЗапросДалееУстановкаПараметраЗатемТекстЗапросаСПовторениемПараметра(Ссылка)
+
+ Запрос = Новый Запрос;
+ Запрос.УстановитьПараметр("Параметр", Ссылка);
+ Запрос.Текст = "ВЫБРАТЬ
+ | Справочник1.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник1 КАК Справочник1
+ |ГДЕ
+ | Справочник1.Ссылка = &Параметр
+ |
+ |ОБЪЕДИНИТЬ ВСЕ
+ |
+ |ВЫБРАТЬ
+ | Справочник1.Ссылка КАК Ссылка
+ |ИЗ
+ | Справочник.Справочник1 КАК Справочник1
+ |ГДЕ
+ | Справочник1.Ссылка = &Параметр
+ |"; // нет ошибки
+
+ //РезультатЗапроса = Запрос7.Выполнить();
+
+КонецПроцедуры
+
+Процедура Десятый_СначалаНовыйЗапросДалееУстановкаПараметраЗатемТекстЗапросаСПовторениемПараметра(Ссылка)
+
+ ТекстЗапроса = "ВЫБРАТЬ * ИЗ &ИмяТаблицы ГДЕ ЛОЖЬ"; // не ошибка
+ ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&ИмяТаблицы", ИсточникДанных);
+ Запрос = Новый Запрос(ТекстЗапроса);
+
+ //РезультатЗапроса = Запрос7.Выполнить();
+
+КонецПроцедуры