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.Выполнить(); + +КонецПроцедуры