diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad672efd..2d998e845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ #### Код модулей +- Проверка когнитивной сложности методов +- Проверка цикломатической сложности методов - В проверку использования нерекомендуемых методов (use-non-recommended-method) добавлен метод ПолучитьФорму(GetForm) #### Запросы diff --git a/bundles/com.e1c.v8codestyle.bsl/markdown/cognitive-complexity.md b/bundles/com.e1c.v8codestyle.bsl/markdown/cognitive-complexity.md new file mode 100644 index 000000000..07be3330d --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/markdown/cognitive-complexity.md @@ -0,0 +1,158 @@ +# The cognitive complexity of the method is exceeded + +The high score of the cognitive complexity to complicate perception and maintainability of the written code. + +The most effective way to reduce the complexity score is to decompose the code and simplify logical expressions. + +## Compute rules: + +### Increments: + +- Conditional statements: +```bsl +If Conditional1 Then // +1 point + // Code +ElseIf Conditional2 Then // +1 point + // Code +Else // +1 point + // Code +EndIf; +``` +- Trenary operator: +```bsl +?(conditional, positive_code, negative_code); // +1 point +``` +- Loops: +```bsl +For Each Element In Collection Do // +1 point + // code +EndDo; +``` +```bsl +For index = 1 To N Do // +1 point + // code +EndDo; +``` +```bsl +While True Do // +1 point + // code +EndDo; +``` +- Exception block: +```bsl +... +Exception // +1 point + // code +EndTry; +``` +- GoTo: +```bsl +Goto ~Label; // +1 point +``` +- boolean operands AND, OR +```bsl + conditional1 = predicate1 OR predicate2; // +1 point + conditional2 = predicate1 AND predicate2; // +1 point +``` +- recursive call: +```bsl +Procedure RecursiveCall(Collection) + If conditional Then + RecursiveCall(...); // +1 point + EndIf; +EndProcedure +``` + +### Nesting increments: + +- IF-part of conditional statement: +```bsl +If conditional1 then // +nesting level + // code +ElseIf conditional2 then + // code +Else + // code +EndIf; +``` +- Trenary operator: +```bsl + ?(conditional, value1, value2); // +nesting level +``` +- Loops: +```bsl +For Each Element In Collection Do // +nesting level + // code +EndDo; +``` +```bsl +For index = 1 To N Do // +nesting level + // code +EndDo; +``` +```bsl +While True Do // +nesting level + // code +EndDo; +``` +- exception block: +```bsl +... +Exception // +nesting level + // code +EndTry; +``` + +### Nesting level: + +- Conditional statements: +```bsl +If Conditional1 Then // increment depth + // Code +ElseIf Conditional2 Then // increment depth + // Code +Else // increment depth + // Code +EndIf; +``` + +- Trenary operator: +```bsl +?( + conditional, + // increment depth, + // increment depth +) +``` +- Loops: +```bsl +For Each Element In Collection Do + // increment depth +EndDo; +``` +```bsl +For index = 1 To N Do + // increment depth +EndDo; +``` +```bsl +While True Do + // increment depth +EndDo; +``` +- Exception block: +```bsl +... +Exception + // increment depth +EndTry; +``` +- Nesting method: +```bsl +Method( + // increment depth + NestingMethod( + // increment depth + ) +); +``` diff --git a/bundles/com.e1c.v8codestyle.bsl/markdown/cyclomatic-complexity.md b/bundles/com.e1c.v8codestyle.bsl/markdown/cyclomatic-complexity.md new file mode 100644 index 000000000..17ab654c1 --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/markdown/cyclomatic-complexity.md @@ -0,0 +1,65 @@ +# The cyclomatic complexity of the method is exceeded + +Thomas J. McCabe developed a cyclomatic metric for code testing tasks. +His proposed method calculates the number of linearly independent program execution threads. +The difficulty value corresponds to the number of tests required. + +The most effective way to reduce the complexity score is to decompose the code and simplify logical expressions. + +## Compute rules: + +### Increments: + +- Loops: +```bsl +For Item In Collection Do + // code +EndDo; +``` +```bsl +For index = 1 To N Do + // code +EndDo; +``` +```bsl +While Conditional Do + // code +EndDo; +``` +- Conditional statements: +```bsl +If Conditional Then // +1 point + // code +EndIf; + +If Conditional Then // too +1 point + // code +Else + // code +EndIf; +``` +```bsl +If Conditional1 Then // +1 point + // code +ElseIf Conditional2 Then // +1 additional point + // code +ElseIf Conditional3 Then // +1 additional point + // code +Else // the Else does not increase the complexity + // code +EndIf; +``` +- Try-Exception blocks: +```bsl +Try + // code +Exception + // code +EndTry; +``` +- Booleands operands: AND, OR +- Trenary operator: +```bsl +?(Coditional, Value1, Value2); +``` +- Procedure or Function initially has a complexity equal to 1 diff --git a/bundles/com.e1c.v8codestyle.bsl/markdown/ru/cognitive-complexity.md b/bundles/com.e1c.v8codestyle.bsl/markdown/ru/cognitive-complexity.md new file mode 100644 index 000000000..ca78b9fd0 --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/markdown/ru/cognitive-complexity.md @@ -0,0 +1,158 @@ +# Превышение когнитивной сложности метода + +Высокое значение показателя говорит о сложности восприятия и сопровождаемости написанного кода. + +Наиболее эффективным способом уменьшения показателя сложности является декомпозиция кода и упрощение логических выражений. + +## Правила вычисления: + +### Конструкции увеличивающие сложность: + +- Ветви условного оператора: +```bsl +Если Условие1 Тогда // +1 к сложности + // код +ИначеЕсли Условие2 Тогда // +1 к сложности + // код +Иначе // +1 к сложности + // код +КонецЕсли; +``` +- тренарный оператор: +```bsl +?(условие, позитивный исход, негативный исход); // +1 к сложности +``` +- циклы: +```bsl +Для Каждого Элемент Из Коллекция Цикл // +1 к сложности + // код +КонецЦикла; +``` +```bsl +Для Индекс = 1 По Граница Цикл // +1 к сложности + // код +КонецЦикла; +``` +```bsl +Пока Условия Цикл // +1 к сложности + // код +КонецЦикла; +``` +- ветвь исключения в блоке попытки: +```bsl +... +Исключение // +1 к сложности + // код +КонецПопытки; +``` +- переход к метке +```bsl +Перейти ~Метка; // +1 к сложности +``` +- логические операнды И, ИЛИ +```bsl + Условие1 = Предикат1 ИЛИ Предикат2; // +1 к сложности + Условие2 = Предикат1 И Предикат2; // +1 к сложности +``` +- рекурсивный вызов +```bsl +Процедура РекурсивныйОбход(Коллекция) + Если Условие Тогда + РекурсивныйОбход(...); // +1 к сложности + КонецЕсли; +КонецПроцедуры +``` + +### Конструкции получающие дополнительный штраф на уровень вложенности: + +- Условный оператор (не зависимо от количества ветвей): +```bsl +Если Условие1 Тогда // штраф +уровень_вложенности + // код +ИначеЕсли Условие2 Тогда + // код +Иначе + // код +КонецЕсли; +``` +- тренарный оператор: +```bsl + ?(Условие, Значение1, Значение2); // штраф +уровень_вложенности +``` +- циклы: +```bsl +Для Каждого Элемент Из Коллекция Цикл // штраф +уровень_вложенности + // код +КонецЦикла; +``` +```bsl +Для Индекс = 1 По Граница Цикл // штраф +уровень_вложенности + // код +КонецЦикла; +``` +```bsl +Пока Условия Цикл // штраф +уровень_вложенности + // код +КонецЦикла; +``` +- ветвь исключения в попытке: +```bsl +... +Исключение // штраф +уровень_вложенности + // код +КонецПопытки; +``` + +### Конструкции увеличивающие уровень вложенности: + +- ветви условного оператора: +```bsl +Если Условие1 Тогда + // увеличение вложенности +ИначеЕсли Условие2 Тогда + // увеличение вложенности +Иначе + // увеличение вложенности +КонецЕсли; +``` + +- тренарный оператор: +```bsl +?( + условие, + // увеличение вложенности, + // увеличение вложенности +) +``` +- циклы: +```bsl +Для Каждого Элемент Из Коллекция Цикл + // увеличение вложенности +КонецЦикла; +``` +```bsl +Для Индекс = 1 По Граница Цикл + // увеличение вложенности +КонецЦикла; +``` +```bsl +Пока Условия Цикл + // увеличение вложенности +КонецЦикла; +``` +- ветвь исключения в блоке попытки: +```bsl +... +Исключение + // увеличение вложенности +КонецПопытки; +``` +- вложенный вызов: +```bsl +ВызовМетода( + // увеличение вложенности + ВложенныйВызов( + // увеличение вложенности + ) +); +``` diff --git a/bundles/com.e1c.v8codestyle.bsl/markdown/ru/cyclomatic-complexity.md b/bundles/com.e1c.v8codestyle.bsl/markdown/ru/cyclomatic-complexity.md new file mode 100644 index 000000000..310b845d9 --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/markdown/ru/cyclomatic-complexity.md @@ -0,0 +1,67 @@ +# Превышение цикломатической сложности метода + +Томас Дж. Маккейб для задач тестирования кода разработал цикломатическую метрику. +Предложенный им метод вычисляет количество линейно независимых потоков исполнения программы. +Значение сложности соответствует числу необходимы тестов. + +Наиболее эффективным способом уменьшения показателя сложности является декомпозиция кода и упрощение логических выражений. + +## Правила вычисления: + +### Конструкции, увеличивающие цикломатическую сложность: + +- Циклы: +```bsl +Для Каждого Элемент Из Коллекция Цикл + // код +КонецЦикла; +``` +```bsl +Для Индекс = 1 По Гранца Цикл + // код +КонецЦикла; +``` +```bsl +Пока Условие Цикл + // код +КонецЦикла; +``` +- Условия: +```bsl +// ветвление увеличивает цикломатическу сложность на единицу, не зависимо от наличия/отсутствия блока Иначе +Если Условие Тогда // +1 к сложности + // Код +КонецЕсли; + +Если Условие Тогда // тоже +1 к сложности за всю конструкцию + // Код +Иначе + // Код +КонецЕсли; +``` +```bsl +// Каждый блок ИначеЕсли увеличивает сложность на дополнительную единицу +Если Условие1 Тогда // +1 за условный блок + // код +ИначеЕсли Условие2 Тогда // +1 дополнительно за ветвь + // код +ИначеЕсли Условие3 Тогда // +1 дополнительно за ветвь + // код +Иначе // блок иначе не увеличивает сложность + // код +КонецЕсли; +``` +- Блок обработки исключения: +```bsl +Попытка + // код +Исключение + // код +КонецПопытки; +``` +- Логические операции И, ИЛИ +- Тренарный оператор: +```bsl +?(Условие, Значение1, Значение2); +``` +- Процедура или Функция изначально имеет сложность равную 1 diff --git a/bundles/com.e1c.v8codestyle.bsl/plugin.xml b/bundles/com.e1c.v8codestyle.bsl/plugin.xml index 7d684c473..a52286265 100644 --- a/bundles/com.e1c.v8codestyle.bsl/plugin.xml +++ b/bundles/com.e1c.v8codestyle.bsl/plugin.xml @@ -347,6 +347,14 @@ category="com.e1c.v8codestyle.bsl" class="com.e1c.v8codestyle.internal.bsl.ExecutableExtensionFactory:com.e1c.v8codestyle.bsl.check.DeprecatedProcedureOutsideDeprecatedRegionCheck"> + + + + diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/CognitiveComplexityProcessor.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/CognitiveComplexityProcessor.java new file mode 100644 index 000000000..a401f183e --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/CognitiveComplexityProcessor.java @@ -0,0 +1,249 @@ +/******************************************************************************* + * Copyright (C) 2023, 1C-Soft LLC and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Manaev Konstantin - initial API and implementation + *******************************************************************************/ +package com.e1c.v8codestyle.bsl; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.common.util.EList; + +import com._1c.g5.v8.dt.bsl.model.BinaryExpression; +import com._1c.g5.v8.dt.bsl.model.BinaryOperation; +import com._1c.g5.v8.dt.bsl.model.Conditional; +import com._1c.g5.v8.dt.bsl.model.DynamicFeatureAccess; +import com._1c.g5.v8.dt.bsl.model.Expression; +import com._1c.g5.v8.dt.bsl.model.FeatureAccess; +import com._1c.g5.v8.dt.bsl.model.FunctionStyleCreator; +import com._1c.g5.v8.dt.bsl.model.GotoStatement; +import com._1c.g5.v8.dt.bsl.model.IfStatement; +import com._1c.g5.v8.dt.bsl.model.IndexAccess; +import com._1c.g5.v8.dt.bsl.model.Invocation; +import com._1c.g5.v8.dt.bsl.model.LoopStatement; +import com._1c.g5.v8.dt.bsl.model.Method; +import com._1c.g5.v8.dt.bsl.model.OperatorStyleCreator; +import com._1c.g5.v8.dt.bsl.model.SimpleStatement; +import com._1c.g5.v8.dt.bsl.model.Statement; +import com._1c.g5.v8.dt.bsl.model.StaticFeatureAccess; +import com._1c.g5.v8.dt.bsl.model.TryExceptStatement; +import com._1c.g5.v8.dt.bsl.model.UnaryExpression; +import com._1c.g5.v8.dt.bsl.model.WhileStatement; + +/** + * The cognitive complexity processor. + * + * @author Manaev Konstantin + */ +public final class CognitiveComplexityProcessor + implements IComplexityProcessor +{ + private static final String TRENARY_OPERATOR = "?"; //$NON-NLS-1$ + + @Override + public int compute(Method method, IProgressMonitor monitor) + { + int nestedLevel = 0; + int complexityValue = 0; + String uniqueName = method.getName(); + for (Statement statement : method.allStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(statement, nestedLevel, uniqueName, monitor); + } + return complexityValue; + } + + private int computeStatementComplexity(Statement statement, int nestedLevel, String methodName, + IProgressMonitor monitor) + { + int complexityValue = 0; + if (statement instanceof LoopStatement) + { + complexityValue += 1 + nestedLevel; + if (statement instanceof WhileStatement) + { + complexityValue += computeExpressionComplexity(((WhileStatement)statement).getPredicate(), nestedLevel, + methodName, monitor); + } + for (Statement substatement : ((LoopStatement)statement).getStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(substatement, nestedLevel + 1, methodName, monitor); + } + } + else if (statement instanceof IfStatement) + { + complexityValue += nestedLevel; + IfStatement ifStatement = (IfStatement)statement; + Conditional ifPart = ifStatement.getIfPart(); + complexityValue += computeConditionalComplexity(ifPart, nestedLevel, methodName, monitor); + for (Conditional elseIfPart : ifStatement.getElsIfParts()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeConditionalComplexity(elseIfPart, nestedLevel, methodName, monitor); + } + EList substatements = ifStatement.getElseStatements(); + if (!substatements.isEmpty()) + { + complexityValue++; + for (Statement substatement : substatements) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(substatement, nestedLevel + 1, methodName, monitor); + } + } + } + else if (statement instanceof TryExceptStatement) + { + TryExceptStatement tryExceptStatement = (TryExceptStatement)statement; + for (Statement substatement : tryExceptStatement.getTryStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(substatement, nestedLevel, methodName, monitor); + } + for (Statement substatement : tryExceptStatement.getExceptStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += nestedLevel + 1; + complexityValue += computeStatementComplexity(substatement, nestedLevel + 1, methodName, monitor); + } + } + else if (statement instanceof GotoStatement) + { + complexityValue++; + } + else if (statement instanceof SimpleStatement) + { + SimpleStatement simpleStatement = (SimpleStatement)statement; + complexityValue += computeExpressionComplexity(simpleStatement.getLeft(), nestedLevel, methodName, monitor); + Expression right = simpleStatement.getRight(); + if (right != null) + { + complexityValue += computeExpressionComplexity(right, nestedLevel, methodName, monitor); + } + } + + return complexityValue; + } + + private int computeConditionalComplexity(Conditional conditional, int nestedLevel, String methodName, + IProgressMonitor monitor) + { + int complexityValue = 1; + complexityValue += computeExpressionComplexity(conditional.getPredicate(), nestedLevel, methodName, monitor); + for (Statement substatement : conditional.getStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(substatement, nestedLevel + 1, methodName, monitor); + } + return complexityValue; + } + + private int computeExpressionComplexity(Expression expression, int nestedLevel, String methodName, + IProgressMonitor monitor) + { + int complexityValue = 0; + if (expression instanceof BinaryExpression) + { + BinaryExpression binaryExpression = (BinaryExpression)expression; + BinaryOperation operation = binaryExpression.getOperation(); + if (operation == BinaryOperation.AND || operation == BinaryOperation.OR) + { + complexityValue++; + } + complexityValue += + computeExpressionComplexity(binaryExpression.getLeft(), nestedLevel, methodName, monitor); + complexityValue += + computeExpressionComplexity(binaryExpression.getRight(), nestedLevel, methodName, monitor); + } + else if (expression instanceof UnaryExpression) + { + complexityValue += computeExpressionComplexity(((UnaryExpression)expression).getOperand(), nestedLevel, + methodName, monitor); + } + else if (expression instanceof DynamicFeatureAccess) + { + complexityValue += computeExpressionComplexity(((DynamicFeatureAccess)expression).getSource(), nestedLevel, + methodName, monitor); + } + else if (expression instanceof Invocation) + { + Invocation invocation = (Invocation)expression; + + FeatureAccess method = invocation.getMethodAccess(); + if (method.getName().equals(TRENARY_OPERATOR)) + { + complexityValue += 1 + nestedLevel; + } + else if (method instanceof StaticFeatureAccess && method.getName().equalsIgnoreCase(methodName)) + { + complexityValue++; + } + for (Expression parameter : invocation.getParams()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeExpressionComplexity(parameter, nestedLevel + 1, methodName, monitor); + } + } + else if (expression instanceof IndexAccess) + { + IndexAccess indexAccess = (IndexAccess)expression; + complexityValue += computeExpressionComplexity(indexAccess.getSource(), nestedLevel, methodName, monitor); + complexityValue += computeExpressionComplexity(indexAccess.getIndex(), nestedLevel, methodName, monitor); + } + else if (expression instanceof FunctionStyleCreator) + { + FunctionStyleCreator creator = (FunctionStyleCreator)expression; + complexityValue += + computeExpressionComplexity(creator.getTypeNameExpression(), nestedLevel, methodName, monitor); + Expression params = creator.getParamsExpression(); + if (params != null) + { + complexityValue += computeExpressionComplexity(params, nestedLevel, methodName, monitor); + } + } + else if (expression instanceof OperatorStyleCreator) + { + for (Expression parameter : ((OperatorStyleCreator)expression).getParams()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeExpressionComplexity(parameter, nestedLevel, methodName, monitor); + } + } + return complexityValue; + } +} diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/CyclomaticComplexityProcessor.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/CyclomaticComplexityProcessor.java new file mode 100644 index 000000000..b8b847960 --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/CyclomaticComplexityProcessor.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (C) 2023, 1C-Soft LLC and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Manaev Konstantin - initial API and implementation + *******************************************************************************/ +package com.e1c.v8codestyle.bsl; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.common.util.EList; + +import com._1c.g5.v8.dt.bsl.model.BinaryExpression; +import com._1c.g5.v8.dt.bsl.model.BinaryOperation; +import com._1c.g5.v8.dt.bsl.model.Conditional; +import com._1c.g5.v8.dt.bsl.model.DynamicFeatureAccess; +import com._1c.g5.v8.dt.bsl.model.Expression; +import com._1c.g5.v8.dt.bsl.model.FeatureAccess; +import com._1c.g5.v8.dt.bsl.model.FunctionStyleCreator; +import com._1c.g5.v8.dt.bsl.model.IfStatement; +import com._1c.g5.v8.dt.bsl.model.IndexAccess; +import com._1c.g5.v8.dt.bsl.model.Invocation; +import com._1c.g5.v8.dt.bsl.model.LoopStatement; +import com._1c.g5.v8.dt.bsl.model.Method; +import com._1c.g5.v8.dt.bsl.model.OperatorStyleCreator; +import com._1c.g5.v8.dt.bsl.model.SimpleStatement; +import com._1c.g5.v8.dt.bsl.model.Statement; +import com._1c.g5.v8.dt.bsl.model.TryExceptStatement; +import com._1c.g5.v8.dt.bsl.model.UnaryExpression; +import com._1c.g5.v8.dt.bsl.model.WhileStatement; + +/** + * The cyclomatic complexity processor. + * + * @author Manaev Konstantin + */ +public final class CyclomaticComplexityProcessor + implements IComplexityProcessor +{ + + private static final String TRENARY_OPERATOR = "?"; //$NON-NLS-1$ + + @Override + public int compute(Method method, IProgressMonitor monitor) + { + int complexityValue = 1; + String uniqueName = method.getName(); + for (Statement statement : method.allStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(statement, uniqueName, monitor); + } + return complexityValue; + } + + private int computeStatementComplexity(Statement statement, String methodName, + IProgressMonitor monitor) + { + int complexityValue = 0; + if (statement instanceof LoopStatement) + { + complexityValue++; + if (statement instanceof WhileStatement) + { + complexityValue += computeExpressionComplexity(((WhileStatement)statement).getPredicate(), + methodName, monitor); + } + for (Statement substatement : ((LoopStatement)statement).getStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(substatement, methodName, monitor); + } + } + else if (statement instanceof IfStatement) + { + IfStatement ifStatement = (IfStatement)statement; + Conditional ifPart = ifStatement.getIfPart(); + complexityValue += computeConditionalComplexity(ifPart, methodName, monitor); + for (Conditional elseIfPart : ifStatement.getElsIfParts()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeConditionalComplexity(elseIfPart, methodName, monitor); + } + EList substatements = ifStatement.getElseStatements(); + if (!substatements.isEmpty()) + { + for (Statement substatement : substatements) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(substatement, methodName, monitor); + } + } + } + else if (statement instanceof TryExceptStatement) + { + TryExceptStatement tryExceptStatement = (TryExceptStatement)statement; + for (Statement substatement : tryExceptStatement.getTryStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(substatement, methodName, monitor); + } + for (Statement substatement : tryExceptStatement.getExceptStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue++; + complexityValue += computeStatementComplexity(substatement, methodName, monitor); + } + } + else if (statement instanceof SimpleStatement) + { + SimpleStatement simpleStatement = (SimpleStatement)statement; + complexityValue += computeExpressionComplexity(simpleStatement.getLeft(), methodName, monitor); + Expression right = simpleStatement.getRight(); + if (right != null) + { + complexityValue += computeExpressionComplexity(right, methodName, monitor); + } + } + + return complexityValue; + } + + private int computeConditionalComplexity(Conditional conditional, String methodName, + IProgressMonitor monitor) + { + int complexityValue = 1; + complexityValue += computeExpressionComplexity(conditional.getPredicate(), methodName, monitor); + for (Statement substatement : conditional.getStatements()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeStatementComplexity(substatement, methodName, monitor); + } + return complexityValue; + } + + private int computeExpressionComplexity(Expression expression, String methodName, + IProgressMonitor monitor) + { + int complexityValue = 0; + if (expression instanceof BinaryExpression) + { + BinaryExpression binaryExpression = (BinaryExpression)expression; + BinaryOperation operation = binaryExpression.getOperation(); + if (operation == BinaryOperation.AND || operation == BinaryOperation.OR) + { + complexityValue++; + } + complexityValue += + computeExpressionComplexity(binaryExpression.getLeft(), methodName, monitor); + complexityValue += + computeExpressionComplexity(binaryExpression.getRight(), methodName, monitor); + } + else if (expression instanceof UnaryExpression) + { + complexityValue += computeExpressionComplexity(((UnaryExpression)expression).getOperand(), + methodName, monitor); + } + else if (expression instanceof DynamicFeatureAccess) + { + complexityValue += computeExpressionComplexity(((DynamicFeatureAccess)expression).getSource(), + methodName, monitor); + } + else if (expression instanceof Invocation) + { + Invocation invocation = (Invocation)expression; + + FeatureAccess method = invocation.getMethodAccess(); + if (method.getName().equals(TRENARY_OPERATOR)) + { + complexityValue++; + } + for (Expression parameter : invocation.getParams()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeExpressionComplexity(parameter, methodName, monitor); + } + } + else if (expression instanceof IndexAccess) + { + IndexAccess indexAccess = (IndexAccess)expression; + complexityValue += computeExpressionComplexity(indexAccess.getSource(), methodName, monitor); + complexityValue += computeExpressionComplexity(indexAccess.getIndex(), methodName, monitor); + } + else if (expression instanceof FunctionStyleCreator) + { + FunctionStyleCreator creator = (FunctionStyleCreator)expression; + complexityValue += + computeExpressionComplexity(creator.getTypeNameExpression(), methodName, monitor); + Expression params = creator.getParamsExpression(); + if (params != null) + { + complexityValue += computeExpressionComplexity(params, methodName, monitor); + } + } + else if (expression instanceof OperatorStyleCreator) + { + for (Expression parameter : ((OperatorStyleCreator)expression).getParams()) + { + if (monitor.isCanceled()) + { + return 0; + } + complexityValue += computeExpressionComplexity(parameter, methodName, monitor); + } + } + return complexityValue; + } +} diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/IComplexityProcessor.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/IComplexityProcessor.java new file mode 100644 index 000000000..cedf6bd7b --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/IComplexityProcessor.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2023, 1C-Soft LLC and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Manaev Konstantin - initial API and implementation + *******************************************************************************/ +package com.e1c.v8codestyle.bsl; + +import org.eclipse.core.runtime.IProgressMonitor; + +import com._1c.g5.v8.dt.bsl.model.Method; + +/** + * The processor of method's complexity. + * This processor compute complexity score by method. + * + * + * @author Manaev Konstantin + */ +public interface IComplexityProcessor +{ + + /** + * Compute complexity score by method. + * + * @param method the method, cannot be {@code null}. + * @param monitor the progress monitor, cannot be {@code null}. + * @return score of complexity + */ + int compute(Method method, IProgressMonitor monitor); +} diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/CognitiveComplexityCheck.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/CognitiveComplexityCheck.java new file mode 100644 index 000000000..103ee55bb --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/CognitiveComplexityCheck.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (C) 2023, 1C-Soft LLC and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * 1C-Soft LLC - initial API and implementation + * Manaev Konstantin - issue #1117 + *******************************************************************************/ +package com.e1c.v8codestyle.bsl.check; + +import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.METHOD; +import static com._1c.g5.v8.dt.mcore.McorePackage.Literals.NAMED_ELEMENT__NAME; + +import java.text.MessageFormat; + +import org.eclipse.core.runtime.IProgressMonitor; + +import com._1c.g5.v8.dt.bsl.model.Method; +import com.e1c.g5.v8.dt.check.CheckComplexity; +import com.e1c.g5.v8.dt.check.ICheckParameters; +import com.e1c.g5.v8.dt.check.components.BasicCheck; +import com.e1c.g5.v8.dt.check.settings.IssueSeverity; +import com.e1c.g5.v8.dt.check.settings.IssueType; +import com.e1c.v8codestyle.bsl.CognitiveComplexityProcessor; +import com.e1c.v8codestyle.bsl.IComplexityProcessor; +import com.e1c.v8codestyle.check.CommonSenseCheckExtension; +import com.e1c.v8codestyle.internal.bsl.BslPlugin; + +/** + * Checks the method that a cognitive complexity is less a threshold. + * + * @author Manaev Konstantin + */ +public final class CognitiveComplexityCheck + extends BasicCheck +{ + + private static final String CHECK_ID = "cognitive-complexity"; //$NON-NLS-1$ + private static final String PARAM_COMPLEXTITY_THRESHOLD = "complexityThreshold"; //$NON-NLS-1$ + private static final String DEFAULT_COMPLEXITY_THRESHOLD = "15"; //$NON-NLS-1$ + + @Override + public String getCheckId() + { + return CHECK_ID; + } + + @Override + protected void configureCheck(CheckConfigurer builder) + { + builder.title(Messages.CognitiveComplexityCheck_title) + .description(MessageFormat.format(Messages.CognitiveComplexityCheck_description, DEFAULT_COMPLEXITY_THRESHOLD)) + .complexity(CheckComplexity.NORMAL) + .severity(IssueSeverity.MINOR) + .issueType(IssueType.WARNING) + .extension(new CommonSenseCheckExtension(getCheckId(), BslPlugin.PLUGIN_ID)) + .module() + .checkedObjectType(METHOD) + .parameter(PARAM_COMPLEXTITY_THRESHOLD, Integer.class, DEFAULT_COMPLEXITY_THRESHOLD, + Messages.CognitiveComplexityCheck_param_threshold_name); + } + + @Override + protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters, + IProgressMonitor monitor) + { + + Method method = (Method)object; + if (method != null) + { + IComplexityProcessor processor = new CognitiveComplexityProcessor(); + int complexityValue = processor.compute(method, monitor); + + int complexityThreshold = parameters.getInt(PARAM_COMPLEXTITY_THRESHOLD); + if (complexityValue > complexityThreshold) + { + resultAceptor.addIssue(MessageFormat.format(Messages.CognitiveComplexityCheck_issue_message, + Integer.toString(complexityValue), Integer.toString(complexityThreshold)), NAMED_ELEMENT__NAME); + } + } + + } + +} diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/CyclomaticComplexityCheck.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/CyclomaticComplexityCheck.java new file mode 100644 index 000000000..2b9e0b1b3 --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/CyclomaticComplexityCheck.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (C) 2023, 1C-Soft LLC and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * 1C-Soft LLC - initial API and implementation + * Manaev Konstantin - issue #1117 + *******************************************************************************/ +package com.e1c.v8codestyle.bsl.check; + +import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.METHOD; +import static com._1c.g5.v8.dt.mcore.McorePackage.Literals.NAMED_ELEMENT__NAME; + +import java.text.MessageFormat; + +import org.eclipse.core.runtime.IProgressMonitor; + +import com._1c.g5.v8.dt.bsl.model.Method; +import com.e1c.g5.v8.dt.check.CheckComplexity; +import com.e1c.g5.v8.dt.check.ICheckParameters; +import com.e1c.g5.v8.dt.check.components.BasicCheck; +import com.e1c.g5.v8.dt.check.settings.IssueSeverity; +import com.e1c.g5.v8.dt.check.settings.IssueType; +import com.e1c.v8codestyle.bsl.CyclomaticComplexityProcessor; +import com.e1c.v8codestyle.bsl.IComplexityProcessor; +import com.e1c.v8codestyle.check.CommonSenseCheckExtension; +import com.e1c.v8codestyle.internal.bsl.BslPlugin; + +public final class CyclomaticComplexityCheck + extends BasicCheck +{ + private static final String CHECK_ID = "cyclomatic-complexity"; //$NON-NLS-1$ + private static final String PARAM_COMPLEXTITY_THRESHOLD = "complexityThreshold"; //$NON-NLS-1$ + private static final String DEFAULT_COMPLEXITY_THRESHOLD = "20"; //$NON-NLS-1$ + + @Override + public String getCheckId() + { + return CHECK_ID; + } + + @Override + protected void configureCheck(CheckConfigurer builder) + { + builder.title(Messages.CyclomaticComplexity_title) + .description(MessageFormat.format(Messages.CyclomaticComplexity_description, DEFAULT_COMPLEXITY_THRESHOLD)) + .complexity(CheckComplexity.NORMAL) + .severity(IssueSeverity.MINOR) + .issueType(IssueType.WARNING) + .extension(new CommonSenseCheckExtension(getCheckId(), BslPlugin.PLUGIN_ID)) + .module() + .checkedObjectType(METHOD) + .parameter(PARAM_COMPLEXTITY_THRESHOLD, Integer.class, DEFAULT_COMPLEXITY_THRESHOLD, + Messages.CyclomaticComplexity_param_threshold_name); + } + + @Override + protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters, + IProgressMonitor monitor) + { + + Method method = (Method)object; + if (method != null) + { + IComplexityProcessor processor = new CyclomaticComplexityProcessor(); + int complexityValue = processor.compute(method, monitor); + + int complexityThreshold = parameters.getInt(PARAM_COMPLEXTITY_THRESHOLD); + if (complexityValue > complexityThreshold) + { + resultAceptor.addIssue(MessageFormat.format(Messages.CyclomaticComplexity_issues_message, + Integer.toString(complexityValue), Integer.toString(complexityThreshold)), NAMED_ELEMENT__NAME); + } + } + + } + +} diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/Messages.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/Messages.java index 2846ab91c..d9d209e05 100644 --- a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/Messages.java +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/Messages.java @@ -61,6 +61,14 @@ final class Messages public static String ChangeAndValidateInsteadOfAroundCheck_Use_ChangeAndValidate_instead_of_Around; public static String ChangeAndValidateInsteadOfAroundCheck_title; + public static String CognitiveComplexityCheck_description; + + public static String CognitiveComplexityCheck_issue_message; + + public static String CognitiveComplexityCheck_param_threshold_name; + + public static String CognitiveComplexityCheck_title; + public static String CommitTransactionCheck_Commit_transaction_must_be_in_try_catch; public static String CommitTransactionCheck_No_begin_transaction_for_commit_transaction; @@ -109,6 +117,14 @@ final class Messages public static String ConsecutiveEmptyLines_Title; + public static String CyclomaticComplexity_description; + + public static String CyclomaticComplexity_issues_message; + + public static String CyclomaticComplexity_param_threshold_name; + + public static String CyclomaticComplexity_title; + public static String DeprecatedProcedureOutsideDeprecatedRegionCheck_Deprecated_function_out_of_deprecated_area; public static String DeprecatedProcedureOutsideDeprecatedRegionCheck_description; diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages.properties b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages.properties index 5ce5b72a8..29fae8618 100644 --- a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages.properties +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages.properties @@ -62,6 +62,14 @@ ChangeAndValidateInsteadOfAroundCheck_description = Checks that pragma &ChangeAn ChangeAndValidateInsteadOfAroundCheck_title = Use pragma &ChangeAndValidate instead of &Around +CognitiveComplexityCheck_description=The cognitive complexity of the method is more then {0} + +CognitiveComplexityCheck_issue_message=Reduce the cognitive complexity of the method from {0} to {1} + +CognitiveComplexityCheck_param_threshold_name=Complexity threshold + +CognitiveComplexityCheck_title=The cognitive complexity of the method is exceeded + CommitTransactionCheck_Commit_transaction_must_be_in_try_catch = Commit transaction must be in a try-catch CommitTransactionCheck_No_begin_transaction_for_commit_transaction = There is no begin transaction for commit transaction @@ -96,6 +104,14 @@ ConsecutiveEmptyLines_Sequence_of_empty_lines_between__0__and__1__is_greator_tha ConsecutiveEmptyLines_Title = Consecutive empty lines +CyclomaticComplexity_description=The cyclomatic complexity of the method is more then {0} + +CyclomaticComplexity_issues_message=Reduce the cyclomatic complexity of the method from {0} to {1} + +CyclomaticComplexity_param_threshold_name=Complexity threshold + +CyclomaticComplexity_title=The cyclomatic complexity of the method is exceeded + DeprecatedProcedureOutsideDeprecatedRegionCheck_Deprecated_function_out_of_deprecated_area = The deprecated procedure (function) "{0}" should be placed in the Deprecated region of the Public region in a common module area DeprecatedProcedureOutsideDeprecatedRegionCheck_description = Deprecated procedure (function) should be placed in the Deprecated region of the Public region in a common module area diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages_ru.properties b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages_ru.properties index 8f953cee6..e94aa4a17 100644 --- a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages_ru.properties +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages_ru.properties @@ -62,6 +62,14 @@ ChangeAndValidateInsteadOfAroundCheck_description = Проверяет, что ChangeAndValidateInsteadOfAroundCheck_title = Используется аннотация &ИзменениеИКонтроль вместо &Вместо +CognitiveComplexityCheck_description=Когнитивная сложность метода больше {0} + +CognitiveComplexityCheck_issue_message=Понизьте когнитивную сложность метода с {0} до {1} + +CognitiveComplexityCheck_param_threshold_name=Допустимая сложность + +CognitiveComplexityCheck_title=Превышена когнитивная сложность метода + CommitTransactionCheck_Commit_transaction_must_be_in_try_catch = Вызов "ЗафиксироватьТранзакцию()" находится вне конструкции "Попытка... Исключение" CommitTransactionCheck_No_begin_transaction_for_commit_transaction = Отсутствует вызов "НачатьТранзакцию()", хотя вызываются "ЗафиксироватьТранзакцию()" @@ -100,6 +108,14 @@ ConsecutiveEmptyLines_Sequence_of_empty_lines_between__0__and__1__is_greator_tha ConsecutiveEmptyLines_Title = Последовательность пустых строк +CyclomaticComplexity_description=Цикломатическая сложность больше {0} + +CyclomaticComplexity_issues_message=Понизьте цикломатическую сложность с {0} до {1} + +CyclomaticComplexity_param_threshold_name=Допустимая сложность + +CyclomaticComplexity_title=Превышена цикломатическая сложность + DeprecatedProcedureOutsideDeprecatedRegionCheck_Deprecated_function_out_of_deprecated_area = Устаревшую процедуру (функцию) "{0}" следует перенести в область общего модуля УстаревшиеПроцедурыИФункции, размещенную внутри области ПрограммныйИнтерфейс DeprecatedProcedureOutsideDeprecatedRegionCheck_description = Устаревшую процедуру (функцию) следует перенести в область общего модуля УстаревшиеПроцедурыИФункции, размещенную внутри области ПрограммныйИнтерфейс