diff --git a/docs/diagnostics/UselessTernaryOperator.md b/docs/diagnostics/UselessTernaryOperator.md new file mode 100644 index 00000000000..3bd335b8982 --- /dev/null +++ b/docs/diagnostics/UselessTernaryOperator.md @@ -0,0 +1,47 @@ +# Бесполезный тернарный оператор (UselessTernaryOperator) + +| Тип | Поддерживаются
языки | Важность | Включена
по умолчанию | Время на
исправление (мин) | Теги | +|:-------------:|:-----------------------------:|:----------------:|:------------------------------:|:-----------------------------------:|:-----------------------------------:| +| `Дефект кода` | `BSL` | `Информационный` | `Да` | `1` | `badpractice`
`suspicious` | + + +## Описание диагностики +Размещение в тернарном операторе булевых констант "Истина" или "Ложь" указывает на плохую продуманность кода. + +## Примеры +Бессмысленные операторы + +```Bsl +А = ?(Истина, 1, 0); +``` +```Bsl +А = ?(Б = 1, Истина, Ложь); +``` +```Bsl +А = ?(Б = 0, False, True); +``` + +Подозрительные операторы + +```Bsl +А = ?(Б = 1, True, Истина); +``` +```Bsl +А = ?(Б = 0, 0, False); +``` + +## Сниппеты + + +### Экранирование кода + +```bsl +// BSLLS:UselessTernaryOperator-off +// BSLLS:UselessTernaryOperator-on +``` + +### Параметр конфигурационного файла + +```json +"UselessTernaryOperator": false +``` diff --git a/docs/en/diagnostics/UselessTernaryOperator.md b/docs/en/diagnostics/UselessTernaryOperator.md new file mode 100644 index 00000000000..37d47671a28 --- /dev/null +++ b/docs/en/diagnostics/UselessTernaryOperator.md @@ -0,0 +1,47 @@ +# Useless ternary operator (UselessTernaryOperator) + +| Type | Scope | Severity | Activated
by default | Minutes
to fix | Tags | +|:------------:|:-----:|:--------:|:-----------------------------:|:-----------------------:|:-----------------------------------:| +| `Code smell` | `BSL` | `Info` | `Yes` | `1` | `badpractice`
`suspicious` | + + +## Description +The placement of Boolean constants "True" or "False" in the ternary operator indicates poor code thoughtfulness. + +## Examples +Useless operators + +```Bsl +A = ?(True, 1, 0); +``` +```Bsl +A = ?(B = 1, True, False); +``` +```Bsl +A = ?(B = 0, False, True); +``` + +Suspicious operators + +```Bsl +A = ?(B = 1, True, True); +``` +```Bsl +A = ?(B = 0, 0, False); +``` + +## Snippets + + +### Diagnostic ignorance in code + +```bsl +// BSLLS:UselessTernaryOperator-off +// BSLLS:UselessTernaryOperator-on +``` + +### Parameter for config + +```json +"UselessTernaryOperator": false +``` diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic.java new file mode 100644 index 00000000000..3cbdacdc18b --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic.java @@ -0,0 +1,131 @@ +/* + * 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.context.DocumentContext; +import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata; +import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticScope; +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.providers.CodeActionProvider; +import com.github._1c_syntax.bsl.parser.BSLParser; +import com.github._1c_syntax.mdclasses.mdo.MDLanguage; +import org.antlr.v4.runtime.tree.ParseTree; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionParams; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.TextEdit; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@DiagnosticMetadata( + type = DiagnosticType.CODE_SMELL, + severity = DiagnosticSeverity.INFO, + scope = DiagnosticScope.BSL, + minutesToFix = 1, + tags = { + DiagnosticTag.BADPRACTICE, + DiagnosticTag.SUSPICIOUS + } + +) +public class UselessTernaryOperatorDiagnostic extends AbstractVisitorDiagnostic implements QuickFixProvider { + + private static final int SKIPPED_RULE_INDEX = 0; + + @Override + public ParseTree visitTernaryOperator(BSLParser.TernaryOperatorContext ctx){ + + var exp = ctx.expression(); + var condition = getBooleanToken(exp.get(0)); + var trueBranch = getBooleanToken(exp.get(1)); + var falseBranch = getBooleanToken(exp.get(2)); + + if (condition != SKIPPED_RULE_INDEX) { + diagnosticStorage.addDiagnostic(ctx); + } else if (trueBranch == BSLParser.TRUE && falseBranch == BSLParser.FALSE){ + var dgs = diagnosticStorage.addDiagnostic(ctx); + dgs.ifPresent(diagnostic -> diagnostic.setData(exp.get(0).getText())); + } else if (trueBranch == BSLParser.FALSE && falseBranch == BSLParser.TRUE){ + var dgs = diagnosticStorage.addDiagnostic(ctx); + dgs.ifPresent(diagnostic -> diagnostic.setData(getAdaptedText(exp.get(0).getText()))); + } else if (trueBranch != SKIPPED_RULE_INDEX || falseBranch != SKIPPED_RULE_INDEX){ + diagnosticStorage.addDiagnostic(ctx); + } + + return super.visitTernaryOperator(ctx); + } + + @Override + public List getQuickFixes( + List diagnostics, + CodeActionParams params, + DocumentContext documentContext + ) { + + List textEdits = new ArrayList<>(); + + diagnostics.forEach((Diagnostic diagnostic) -> { + var range = diagnostic.getRange(); + var textEdit = new TextEdit(range, (String) diagnostic.getData()); + textEdits.add(textEdit); + }); + + return CodeActionProvider.createCodeActions( + textEdits, + info.getResourceString("quickFixMessage"), + documentContext.getUri(), + diagnostics + ); + + } + + private String getAdaptedText(String text) { + if(documentContext.getServerContext().getConfiguration().getDefaultLanguage() == MDLanguage.ENGLISH) { + return "NOT (" + text + ")"; + } else { + return "НЕ (" + text + ")"; + } + } + + private int getBooleanToken(BSLParser.ExpressionContext expCtx){ + + var tmpCtx = Optional.of(expCtx) + .filter(ctx -> ctx.children.size() == 1) + .map(ctx -> (BSLParser.MemberContext) ctx.getChild(0)) + .map(ctx -> ctx.getChild(0)) + .filter(BSLParser.ConstValueContext.class::isInstance) + .map(BSLParser.ConstValueContext.class::cast); + + return tmpCtx + .map(ctx -> ctx.getToken(BSLParser.TRUE, 0)) + .map(ctx -> BSLParser.TRUE) + .or(() -> tmpCtx + .map(ctx -> ctx.getToken(BSLParser.FALSE, 0)) + .map(ctx -> BSLParser.FALSE) + ) + .orElse(SKIPPED_RULE_INDEX); + } +} 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..98a0e6477a7 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 @@ -1810,6 +1810,16 @@ "title": "Useless collection iteration", "$id": "#/definitions/UseLessForEach" }, + "UselessTernaryOperator": { + "description": "Useless ternary operator", + "default": true, + "type": [ + "boolean", + "object" + ], + "title": "Useless ternary operator", + "$id": "#/definitions/UselessTernaryOperator" + }, "UsingCancelParameter": { "description": "Using parameter \"Cancel\"", "default": true, diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json index 89715626336..da86fd4819d 100644 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json @@ -413,6 +413,9 @@ "UseLessForEach": { "$ref": "parameters-schema.json#/definitions/UseLessForEach" }, + "UselessTernaryOperator": { + "$ref": "parameters-schema.json#/definitions/UselessTernaryOperator" + }, "UsingCancelParameter": { "$ref": "parameters-schema.json#/definitions/UsingCancelParameter" }, diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic_en.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic_en.properties new file mode 100644 index 00000000000..28d413d8b06 --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic_en.properties @@ -0,0 +1,3 @@ +diagnosticMessage=Useless ternary operator +diagnosticName=Useless ternary operator +quickFixMessage=Fix some useless ternary operators \ No newline at end of file diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic_ru.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic_ru.properties new file mode 100644 index 00000000000..250bcee805a --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnostic_ru.properties @@ -0,0 +1,3 @@ +diagnosticMessage=Бесполезный тернарный оператор +diagnosticName=Бесполезный тернарный оператор +quickFixMessage=Исправить некоторые бесполезные тернарные операторы \ No newline at end of file diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnosticTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnosticTest.java new file mode 100644 index 00000000000..7680c07b506 --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/UselessTernaryOperatorDiagnosticTest.java @@ -0,0 +1,86 @@ +/* + * 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.context.DocumentContext; +import org.eclipse.lsp4j.CodeAction; +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 UselessTernaryOperatorDiagnosticTest extends AbstractDiagnosticTest { + + UselessTernaryOperatorDiagnosticTest() { + super(UselessTernaryOperatorDiagnostic.class); + } + + @Test + void test() { + + List diagnostics = getDiagnostics(); + + assertThat(diagnostics).hasSize(8); + assertThat(diagnostics, true) + .hasRange(1, 4, 1, 26) + .hasRange(2, 4, 2, 25) + .hasRange(3, 4, 3, 26) + .hasRange(4, 4, 4, 25) + .hasRange(5, 4, 5, 21) + .hasRange(6, 4, 6, 22) + .hasRange(7, 4, 7, 19) + .hasRange(8, 4, 8, 18); + + } + + @Test + void testQuickFix() { + + final DocumentContext documentContext = getDocumentContext(); + List diagnostics = getDiagnostics(); + + final Diagnostic directDiagnostic = diagnostics.get(0); + List directQuickFixes = getQuickFixes(directDiagnostic); + assertThat(directQuickFixes).hasSize(1); + final CodeAction directQuickFix = directQuickFixes.get(0); + assertThat(directQuickFix) + .of(diagnosticInstance) + .in(documentContext) + .fixes(directDiagnostic) + .hasChanges(1) + .hasNewText("Б=1"); + + final Diagnostic reversDiagnostic = diagnostics.get(1); + List reversQuickFixes = getQuickFixes(reversDiagnostic); + assertThat(reversQuickFixes).hasSize(1); + final CodeAction reversQuickFix = reversQuickFixes.get(0); + assertThat(reversQuickFix) + .of(diagnosticInstance) + .in(documentContext) + .fixes(reversDiagnostic) + .hasChanges(1) + .hasNewText("NOT (Б=0)"); + } + +} diff --git a/src/test/resources/diagnostics/UselessTernaryOperatorDiagnostic.bsl b/src/test/resources/diagnostics/UselessTernaryOperatorDiagnostic.bsl new file mode 100644 index 00000000000..21c447838b3 --- /dev/null +++ b/src/test/resources/diagnostics/UselessTernaryOperatorDiagnostic.bsl @@ -0,0 +1,14 @@ +// Бессмысленные тернарники +А = ?(Б = 1, Истина, Ложь);// прямой, фиксится в А = Б = 1; +А = ?(Б = 0, False, True);// обратный, фиксится в А = НЕ (Б = 0); +А = ?(Б = 1, True, Истина); +А = ?(Б = 0, Ложь, False); +А = ?(Б = 1, True, 1); +А = ?(Б = 0, 0, False); +А = ?(истина, 1, 0); +А = ?(false, 0, 1); + +// валидный +ОбластьМакета.Параметры.ДебетСубСчета = ОбластьМакета.Параметры.ДебетСубСчета + + ?(ПустаяСтрока(ОбластьМакета.Параметры.ДебетСубСчета), "", ", ") + + СчетДт;