diff --git a/docs/diagnostics/LostVariable.md b/docs/diagnostics/LostVariable.md new file mode 100644 index 00000000000..4535f1abf60 --- /dev/null +++ b/docs/diagnostics/LostVariable.md @@ -0,0 +1,155 @@ +# Потерянная переменная (LostVariable) + + +## Описание диагностики + +Переменной присваивается значение, которое не используется. Далее переменной еще раз присваивается новое значение. +Подобный код приводит к снижению читабельности кода, также может приводить к снижению производительности. +Может возникать при опечатках, "копипасте" или рефакторинге кода, а также из-за проблем в алгоритме. + +В текущей реализации возможны "ложно-положительные" срабатывания, т.е. могут выдаваться замечания к правильному коду, т.к. некоторые варианты кода сложно отличить от правильного. Подробнее в примерах, пункт "Примеры исключений". + +## Примеры + +Неверный код - безусловная повторная переустановка значения +```bsl +МояПеременная = СтрЗаменить(Текст, " ", ""); // замечание +МояПеременная = СтрЗаменить(Текст, ";", ""); +``` +Правильный код +```bsl +МояПеременная = СтрЗаменить(Текст, " ", ""); +МояПеременная = СтрЗаменить(МояПеременная, ";", ""); +``` + +Или другой подозрительный код - повторная переустановка значения при некотором условии и только потом переменная используется +```bsl +МояПеременная = СтрЗаменить(Текст, " ", ""); // замечание +Если Условие Тогда + МояПеременная = СтрЗаменить(Текст, ";", ""); + Возврат МояПеременная; +КонецЕсли; +``` + +А вот такой слегка переделанный код уже не будет выявляться правилом +- т.к. переустановка выполняется не всегда, а только при выполнения условия. +```bsl +МояПеременная = СтрЗаменить(Текст, " ", ""); +Если Условие Тогда + МояПеременная = СтрЗаменить(Текст, ";", ""); +КонецЕсли; +Возврат МояПеременная; +``` + +Часто встречающаяся "ошибка последней строки" +```bsl +ТемаПисьма = ДанныеЗаполнения.Тема;// потерялось +ТекстШаблонаПисьмаHTML = ДанныеЗаполнения.ТекстHTML; +ТекстШаблонаПисьма = ДанныеЗаполнения.Текст; +ТемаПисьма = ДанныеЗаполнения.Тема; // а здесь переопредели +Наименование = ДанныеЗаполнения.Тема; +``` + +Еще подозрительный код - переменная `Файл` меняется несколько раз, и во втором случае ошибочно. +```bsl +Процедура ПолучитьФайлОбработки() + Файл = Новый Файл(КаталогФич); // нет замечания + Если Не Файл.Существует() Тогда + Возврат; + КонецЕсли; + + Файл = Новый Файл(КаталогФич); // замечание + НачальныйКаталог = КаталогФич; + + Файл = Новый Файл(НачальныйКаталог); +КонецПроцедуры +``` + +Пример подозрительного кода - Инициализация переменных в начале метода + +Начальная инициализация может обмануть, т.к. при переустановке переменной значение может получить другой тип. +Или в текущем коде или в будущем, при переделке кода. +```bsl +Функция ПолучитьПутьФайлаВРабочемКаталоге(ДанныеФайла) + + ПолноеИмяФайла = ""; // замечание + ИмяКаталога = РабочийКаталогПользователя(); + + // Сперва пытаемся найти такую запись в регистре сведений. + // тут может быть неточность, т.к. + ПолноеИмяФайла = ДанныеФайла.ПолноеИмяФайлаВРабочемКаталоге; +``` +или похожий, чуть более сложный кейс +``` + РезультатОбновления = НовыйРезультатОбновления(); + + ДвоичныеДанные = СкачатьФайлОбработкиНаКлиентеНаСервере( + ЭтотОбъект.ПараметрыРаботы.АдресПубликацииВебВитрины, + ЭтотОбъект.ПараметрыОбновления); + + Если Не ЗначениеЗаполнено(ДвоичныеДанные) Тогда + РезультатОбновления.ТекстОшибки = "Не удалось скачать файл обработки."; + Возврат РезультатОбновления; + КонецЕсли; + + // реальный метод ниже может вернуть что угодно сейчас или в будущем + РезультатОбновления = ЭтотОбъект.УстановитьОбработкуНаСервере(ДвоичныеДанные, + ЭтотОбъект.ПараметрыРаботы); +``` + +**Примеры исключений** + +- 1 в коллекцию добавляются разные значения без явного переиспользования +```bsl +ВидПрава = ВидыПрав.Добавить(); // будет выдано замечание, хотя код валиден +ВидПрава = ВидыПрав.Добавить(); + +НовыйПереход = ТаблицаПереходовНоваяСтрока("Начало"); // будет выдано замечание, хотя код валиден +НовыйПереход = ТаблицаПереходовНоваяСтрока("НастройкаВыгрузки"); +``` +Код выше вполне валиден, но его можно улучшить и упростить, явно выразив свои намерения, избавившись от присваивания переменным и дублирования имен переменных +Исправленный код +```bsl +ВидыПрав.Добавить(); +ВидыПрав.Добавить(); + +ТаблицаПереходовНоваяСтрока("Начало"); +ТаблицаПереходовНоваяСтрока("НастройкаВыгрузки"); +``` + +Пример полезного замечания для подобных дублей добавления в коллекцию - пример из типовой конфигурации +```bsl +ВидПрава = ВидыПрав.Добавить(); // вот тут ошибка! +ВидПрава = "Просмотр"; // т.к. ниже опечатка, автор кода забыл добавить .Имя +ВидПрава.Интерактивное = Истина; + +ВидПрава = ВидыПрав.Добавить(); +ВидПрава.Имя = "Редактирование"; +``` + +- 2 обработка переменных в блоках Попытка и Исключение +```bsl +Процедура УстановитьБлокировку() + Блокировка = Новый БлокировкаДанных; + + НачатьТранзакцию(); + Попытка + ЭтоОшибкаБлокировки = Истина; // будет выдано замечание, хотя код валиден + Блокировка.Заблокировать(); // здесь возможно исключение, и поэтому до следующей строки выполнение может не дойти + + ЭтоОшибкаБлокировки = Ложь; + ЗафиксироватьТранзакцию(); + Исключение + ОтменитьТранзакцию(); + Если ЭтоОшибкаБлокировки Тогда + ОбработкаОшибкиБлокировки(); + КонецЕсли; + КонецПопытки; +КонецПроцедуры +``` + +## Источники + +* [MITRE, CWE-563 - Assignment to Variable without Use](https://cwe.mitre.org/data/definitions/563.html) +* [MITRE, CWE-1109 - Use of Same Variable for Multiple Purposes](https://cwe.mitre.org/data/definitions/1109.html) +* [PVS-Studio.V763. Parameter is always rewritten in function body before being used.](https://pvs-studio.com/ru/docs/warnings/v763) diff --git a/docs/en/diagnostics/LostVariable.md b/docs/en/diagnostics/LostVariable.md new file mode 100644 index 00000000000..a8ea9df8e4b --- /dev/null +++ b/docs/en/diagnostics/LostVariable.md @@ -0,0 +1,16 @@ +# Lost variable (LostVariable) + + +## Description + + +## Examples + + +## Sources + + diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic.java new file mode 100644 index 00000000000..f4bcba67bfa --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic.java @@ -0,0 +1,437 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2022 + * Alexey Sosnoviy , Nikita Fedkin 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.symbol.MethodSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol; +import com.github._1c_syntax.bsl.languageserver.context.symbol.variable.VariableKind; +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.references.ReferenceIndex; +import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType; +import com.github._1c_syntax.bsl.languageserver.references.model.Reference; +import com.github._1c_syntax.bsl.languageserver.utils.Ranges; +import com.github._1c_syntax.bsl.languageserver.utils.RelatedInformation; +import com.github._1c_syntax.bsl.languageserver.utils.Trees; +import com.github._1c_syntax.bsl.parser.BSLParser; +import com.github._1c_syntax.bsl.parser.BSLParserRuleContext; +import com.github._1c_syntax.bsl.types.ModuleType; +import lombok.RequiredArgsConstructor; +import lombok.Value; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.RuleNode; +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.lsp4j.DiagnosticRelatedInformation; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.SymbolKind; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@DiagnosticMetadata( + type = DiagnosticType.CODE_SMELL, + severity = DiagnosticSeverity.MAJOR, + minutesToFix = 2, + tags = { + DiagnosticTag.SUSPICIOUS, + DiagnosticTag.UNPREDICTABLE, + DiagnosticTag.BADPRACTICE + }, + // TODO сделать флаг для управления работой в модулях объектов и форм? ведь в них могут быть FP + modules = { + ModuleType.CommandModule, + ModuleType.CommonModule, + ModuleType.ManagerModule, + ModuleType.ValueManagerModule, + ModuleType.SessionModule, + ModuleType.UNKNOWN + } +) + +@RequiredArgsConstructor +public class LostVariableDiagnostic extends AbstractDiagnostic { + + private static final Set GlobalVariableKinds = EnumSet.of(VariableKind.GLOBAL, VariableKind.MODULE); + private static final String MODULE_SCOPE_NAME = ""; + private static final String UNUSEDAFTER_MESSAGE = "unusedAfterMessage"; + + private static final Collection EXCLUDED_TOP_RULE_FOR_LOOP = Set.of(BSLParser.RULE_subCodeBlock, BSLParser.RULE_fileCodeBlock); + private static final Collection LOOPS; + + private final ReferenceIndex referenceIndex; + private final Map astBySymbol = new HashMap<>(); + private Map methodContextsByMethodName; + + static { + final var loops = new HashSet<>(EXCLUDED_TOP_RULE_FOR_LOOP); + loops.addAll(Set.of(BSLParser.RULE_forStatement, BSLParser.RULE_forEachStatement, BSLParser.RULE_whileStatement)); + LOOPS = Set.copyOf(loops); + } + + @Override + protected void check() { + methodContextsByMethodName = getMethodContextsByMethodName(); + getFileCodeBlock() + .ifPresent(fileCodeBlock -> methodContextsByMethodName.put(MODULE_SCOPE_NAME, fileCodeBlock)); + + getVariables() + .filter(this::isLostVariable) + .forEach(this::fireIssue); + + astBySymbol.clear(); + methodContextsByMethodName.clear(); + } + + private Map getMethodContextsByMethodName() { + if (documentContext.getSymbolTree().getMethods().isEmpty()){ + return new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + } + return Optional.ofNullable(documentContext.getAst().subs()) + .map(BSLParser.SubsContext::sub) + .map(subContexts -> subContexts.stream() + .map(LostVariableDiagnostic::getMethodNameAndContext) + .collect(Collectors.toMap(Pair::getLeft, Pair::getRight))) + .orElseThrow(); + } + + private static Pair getMethodNameAndContext(BSLParser.SubContext subContext) { + final var subContextOpt = Optional.of(subContext); + final var subName = subContextOpt + .map(BSLParser.SubContext::function) +// .map(functionContext -> Pair.of(functionContext, functionContext.funcDeclaration().subName())) + .map(BSLParser.FunctionContext::funcDeclaration) + .map(BSLParser.FuncDeclarationContext::subName) + .map(BSLParser.SubNameContext::IDENTIFIER) + .map(ParseTree::getText) + .orElseGet(() -> subContextOpt + .map(BSLParser.SubContext::procedure) + .map(BSLParser.ProcedureContext::procDeclaration) + .map(BSLParser.ProcDeclarationContext::subName) + .map(BSLParser.SubNameContext::IDENTIFIER) + .map(ParseTree::getText) + .orElseThrow() + ); + return Pair.of(subName, subContext); + } + + private Optional getFileCodeBlock() { + return Optional.ofNullable(documentContext.getAst().fileCodeBlock()); + } + + private Stream getVariables() { + return documentContext.getSymbolTree().getVariables().stream() + .map(variableSymbol -> Pair.of(getRootSymbol(variableSymbol), variableSymbol)) + .flatMap(pair -> getVarData(pair.getRight(), pair.getLeft()).stream()); + } + + private static SourceDefinedSymbol getRootSymbol(VariableSymbol variableSymbol) { + return variableSymbol.getRootParent(SymbolKind.Method) + .orElseGet(() -> variableSymbol.getRootParent(SymbolKind.Module) + .orElseThrow()); + } + + private List getVarData(VariableSymbol variable, SourceDefinedSymbol methodSymbol) { + List allReferences = referenceIndex.getReferencesTo(variable); + if (allReferences.isEmpty()) { + return Collections.emptyList(); + } + return getConsecutiveDefinitions(variable, allReferences, methodSymbol); + } + + private static List getConsecutiveDefinitions(VariableSymbol variable, List allReferences, + SourceDefinedSymbol methodSymbol) { + List result = new ArrayList<>(); + Reference prev = null; + final var isGlobalVar = GlobalVariableKinds.contains(variable.getKind()); + if (!isGlobalVar && allReferences.get(0).getOccurrenceType() == OccurrenceType.DEFINITION) { + prev = allReferences.get(0); + + var references = allReferences.subList(1, allReferences.size()); + var varData = VarData.of(variable, variable.getVariableNameRange(), allReferences.get(0), + methodSymbol, references); + result.add(varData); + } + final int firstIndex; + if (isGlobalVar){ + firstIndex = 0; + } else { + firstIndex = 1; + } + for (var i = firstIndex; i < allReferences.size(); i++) { + final var current = allReferences.get(i); + if (current.getOccurrenceType() == OccurrenceType.DEFINITION) { + if (prev != null) { + final List references; + if (i < allReferences.size() - 1) { + references = allReferences.subList(i + 1, allReferences.size()); + } else { + references = Collections.emptyList(); + } + var varData = VarData.of(variable, prev.getSelectionRange(), current, + methodSymbol, references); + result.add(varData); + } + prev = current; + continue; + } + prev = null; + } + if (!isGlobalVar && variable.getKind() != VariableKind.PARAMETER){ + final var lastDefinition = getLastDefinition(allReferences); + if (lastDefinition != null){ + result.add(VarData.ofFinished(variable, lastDefinition, methodSymbol)); + } + } + return result; + } + + @Nullable + private static Reference getLastDefinition(List referencesTo) { + var reverseIterator = referencesTo.listIterator(referencesTo.size()); + + if(reverseIterator.hasPrevious()) { + final var ref = reverseIterator.previous(); + if (ref.getOccurrenceType() == OccurrenceType.DEFINITION){ + return ref; + } + } + return null; + } + + private boolean isLostVariable(VarData varData) { + final RuleNode defNode = findDefNode(varData); + + final var codeBlockForLoopIndex = codeBlockForLoopIndex(defNode); + if (codeBlockForLoopIndex.isPresent()) { + // пропускаю неиспользуемый итератор или счетчик цикла, т.е. есть существующее правило + final var isSameLoop = Ranges.compare(varData.defRange, varData.rewriteRange) == 0 && varData.references.isEmpty(); + final var isInnerLoop = Ranges.containsRange(Ranges.create(codeBlockForLoopIndex.get()), varData.rewriteRange); + if (isSameLoop || !isInnerLoop) { + return false; + } + } else if (varData.unusedAfter){ + return true; + } + + var defCodeBlockOpt = getCodeBlock(defNode); + final var rewriteNodeInsideDefCodeBlockOpt = defCodeBlockOpt + .flatMap(context -> Trees.findContextContainsPosition(context, varData.rewriteRange.getStart())); + if (rewriteNodeInsideDefCodeBlockOpt.isEmpty()) { + return false; + } + + var defCodeBlock = defCodeBlockOpt.orElseThrow(); + + var rewriteNode = rewriteNodeInsideDefCodeBlockOpt.get(); + + var rewriteStatement = getRootStatement(rewriteNode); + var rewriteCodeBlock = getCodeBlock(rewriteStatement).orElseThrow(); + + var isInsideSameBlock = defCodeBlock == rewriteCodeBlock; + if (isInsideSameBlock) { + return isLostVariableInSameBlock(varData, defNode, defCodeBlock, rewriteStatement); + } + + return isLostVariableInDifferentBlocks(varData, rewriteStatement, rewriteCodeBlock); + } + + private RuleNode findDefNode(VarData varData) { + final BSLParserRuleContext parentBlockContext; + if (varData.isGlobalOrModuleKind) { + parentBlockContext = findCodeBlockContextByRange(varData.defRange); + } else { + // быстрее сначала найти узел метода в дереве, а потом уже узел переменной в дереве метода + // чтобы постоянно не искать по всему дереву файла + parentBlockContext = getCodeBlockContextBySymbol(varData.parentSymbol, varData.isMethod); + } + return Trees.findContextContainsPosition(parentBlockContext, varData.defRange.getStart()) + .orElseThrow(); + } + + private BSLParserRuleContext findCodeBlockContextByRange(Range range) { + final var methodName = documentContext.getSymbolTree().getMethods().stream() + .filter(methodSymbol -> Ranges.containsRange(methodSymbol.getRange(), range)) + .map(MethodSymbol::getName) + .findFirst() + .orElse(MODULE_SCOPE_NAME); + return Optional.ofNullable(methodContextsByMethodName.get(methodName)) + .filter(Objects::nonNull) + .orElseThrow(); + } + + private BSLParserRuleContext getCodeBlockContextBySymbol(SourceDefinedSymbol method, boolean isMethod) { + if (isMethod) { + return astBySymbol.computeIfAbsent(method, methodSymbol -> + methodContextsByMethodName.get(methodSymbol.getName())); + } + return astBySymbol.computeIfAbsent(method, methodSymbol -> + methodContextsByMethodName.get(MODULE_SCOPE_NAME)); + } + + private static Optional codeBlockForLoopIndex(RuleNode defNode) { + if (defNode instanceof BSLParser.ForStatementContext){ + return Optional.of(((BSLParser.ForStatementContext) defNode) + .codeBlock()); + } else if (defNode instanceof BSLParser.ForEachStatementContext){ + return Optional.of(((BSLParser.ForEachStatementContext) defNode) + .codeBlock()); + } + return Optional.empty(); + } + + private static Optional getCodeBlock(RuleNode context) { + return Trees.getRootNode(context, BSLParser.RULE_codeBlock, BSLParser.CodeBlockContext.class); + } + + private static BSLParser.StatementContext getRootStatement(RuleNode node) { + return Trees.getRootNode(node, BSLParser.RULE_statement, BSLParser.StatementContext.class) + .orElseThrow(); + } + + private static boolean isLostVariableInSameBlock(VarData varData, RuleNode defNode, + BSLParser.CodeBlockContext defCodeBlock, + BSLParser.StatementContext rewriteStatement) { + if (!varData.references.isEmpty() && isRewriteAlreadyContainsFirstReference(varData, rewriteStatement)) { + return false; + } + var defStatement = getRootStatement(defNode); + + var defAndRewriteIsInSameLine = defStatement == rewriteStatement; + if (defAndRewriteIsInSameLine){ + return true; + } + var hasPreprocessorBetween = getStatementsBetween(defCodeBlock, defStatement, rewriteStatement) + .anyMatch(statementContext -> statementContext.preprocessor() != null); + if (hasPreprocessorBetween){ + return false; + } + var hasReturnBetween = getStatementsBetween(defCodeBlock, defStatement, rewriteStatement) + .anyMatch(statementContext -> Trees.findNodeSuchThat(statementContext, BSLParser.RULE_returnStatement) + .isPresent()); + return !hasReturnBetween; + } + + private static Stream getStatementsBetween(BSLParser.CodeBlockContext defCodeBlock, + BSLParser.StatementContext defStatement, + BSLParser.StatementContext rewriteStatement) { + return defCodeBlock.children.stream() + .filter(BSLParser.StatementContext.class::isInstance) + .map(BSLParser.StatementContext.class::cast) + .dropWhile(statementContext -> statementContext != defStatement) + .skip(1) + .takeWhile(statementContext -> statementContext != rewriteStatement) + ; + } + + private static boolean isLostVariableInDifferentBlocks(VarData varData, + BSLParser.StatementContext rewriteStatement, + BSLParser.CodeBlockContext rewriteCodeBlock) { + if (!varData.references.isEmpty() && isRewriteAlreadyContainsFirstReference(varData, rewriteStatement)) { + return false; + } + return !hasReferenceOutsideRewriteBlock(varData.references, rewriteCodeBlock); + } + + private static boolean isRewriteAlreadyContainsFirstReference(VarData varData, + BSLParser.StatementContext rewriteStatement) { + return Ranges.containsRange(Ranges.create(rewriteStatement), varData.references.get(0).getSelectionRange()); + } + + private static boolean hasReferenceOutsideRewriteBlock(List references, BSLParserRuleContext codeBlock) { + return references.stream() + .map(Reference::getSelectionRange) + .anyMatch(range -> Trees.findTerminalNodeContainsPosition(codeBlock, range.getStart()) + .isEmpty()); + } + + private void fireIssue(VarData varData) { + final String message = getMessage(varData); + diagnosticStorage.addDiagnostic(varData.getDefRange(), message, getDiagnosticReferences(varData)); + } + + private String getMessage(VarData varData) { + if (varData.unusedAfter){ + return info.getResourceString(UNUSEDAFTER_MESSAGE, varData.variable.getName()); + } + return info.getMessage(varData.variable.getName()); + } + + private List getDiagnosticReferences(VarData varData) { + final var references = varData.getReferences().stream() + .map(context -> RelatedInformation.create( + documentContext.getUri(), + context.getSelectionRange(), + "+1" + )).collect(Collectors.toList()); + if (varData.unusedAfter) { + return references; + } + final var result = new ArrayList(); + result.add(RelatedInformation.create( + documentContext.getUri(), + varData.rewriteRange, + "+1")); + result.addAll(references); + return result; + } + + @Value + private static class VarData { + VariableSymbol variable; + Range defRange; + Range rewriteRange; + List references; + SourceDefinedSymbol parentSymbol; + boolean isMethod; + boolean isGlobalOrModuleKind; + boolean unusedAfter; + + private static VarData of(VariableSymbol variable, Range defRange, Reference rewriteReference, + SourceDefinedSymbol methodSymbol, List references) { + return new VarData(variable, defRange, + rewriteReference.getSelectionRange(), references, methodSymbol, methodSymbol instanceof MethodSymbol, + GlobalVariableKinds.contains(variable.getKind()), false); + } + + private static VarData ofFinished(VariableSymbol variable, Reference lastDefinition, SourceDefinedSymbol methodSymbol) { + return new VarData(variable, lastDefinition.getSelectionRange(), lastDefinition.getSelectionRange(), + Collections.emptyList(), methodSymbol, methodSymbol instanceof MethodSymbol, + GlobalVariableKinds.contains(variable.getKind()), true); + } + } +} diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndex.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndex.java index 8a06c63893d..2bac6965d30 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndex.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndex.java @@ -89,7 +89,7 @@ public List getReferencesTo(SourceDefinedSymbol symbol) { return symbolOccurrenceRepository.getAllBySymbol(symbolDto) .stream() - .map(this::buildReference) + .map(symbolOccurrence -> buildReference(symbolOccurrence, symbol)) .flatMap(Optional::stream) .collect(Collectors.toList()); } @@ -234,6 +234,16 @@ public void addVariableUsage(URI uri, locationRepository.updateLocation(symbolOccurrence); } + private Optional buildReference(SymbolOccurrence symbolOccurrence, SourceDefinedSymbol symbol) { + var uri = symbolOccurrence.getLocation().getUri(); + var range = symbolOccurrence.getLocation().getRange(); + var occurrenceType = symbolOccurrence.getOccurrenceType(); + + SourceDefinedSymbol from = getFromSymbol(symbolOccurrence); + return Optional.of(new Reference(from, symbol, uri, range, occurrenceType)) + .filter(ReferenceIndex::isReferenceAccessible); + } + private Optional buildReference( SymbolOccurrence symbolOccurrence ) { @@ -282,8 +292,8 @@ private SourceDefinedSymbol getFromSymbol(SymbolOccurrence symbolOccurrence) { .filter(sourceDefinedSymbol -> sourceDefinedSymbol.getSymbolKind() != SymbolKind.Namespace) .filter(symbol -> Ranges.containsPosition(symbol.getRange(), position)) .findFirst() - .or(() -> symbolTree.map(SymbolTree::getModule)) - .orElseThrow(); + .or(() -> symbolTree.map(SymbolTree::getModule)) + .orElseThrow(); } private static boolean isReferenceAccessible(Reference reference) { diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Trees.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Trees.java index 735469e1ce9..45d40027c14 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Trees.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/Trees.java @@ -23,16 +23,19 @@ import com.github._1c_syntax.bsl.parser.BSLParser; import com.github._1c_syntax.bsl.parser.BSLParserRuleContext; -import edu.umd.cs.findbugs.annotations.Nullable; import lombok.experimental.UtilityClass; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.misc.Predicate; import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.RuleNode; import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.v4.runtime.tree.Tree; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.util.Positions; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -69,6 +72,50 @@ public static Collection findAllRuleNodes(ParseTree t, int ruleIndex) return org.antlr.v4.runtime.tree.Trees.findAllRuleNodes(t, ruleIndex); } + /** + /** + * Находит только первый элемент дерева, подходящий по типу элемента. + * Поиск более ограничен в отличие от findAllRuleNodes, который всегда обегает все дерево + * + * @param t узел дерева + * @param ruleIndex искомый тип элемент + * @return первый подходящий узел, если он найден + */ + public static Optional findNodeSuchThat(BSLParserRuleContext t, int ruleIndex) { + return findNodeSuchThat(t, node -> node.getRuleIndex() == ruleIndex); + } + + /** + * Находит только первый элемент дерева, подходящий по условию поиска. + * Поиск более ограничен в отличие от findAllRuleNodes, который всегда обегает все дерево + * + * @param t узел дерева + * @param pred предикат-условие поиска + * @return первый подходящий узел, если он найден + */ + public static Optional findNodeSuchThat(BSLParserRuleContext t, Predicate pred) { + return Optional.ofNullable(findNodeSuchThatInner(t, pred)); + } + + @Nullable + private static BSLParserRuleContext findNodeSuchThatInner(BSLParserRuleContext t, Predicate pred) { + if ( pred.eval(t) ) { + return t; + } + + int n = t.getChildCount(); + for (var i = 0 ; i < n ; i++){ + final var child = t.getChild(i); + if (child instanceof BSLParserRuleContext) { + BSLParserRuleContext u = findNodeSuchThatInner((BSLParserRuleContext)child, pred); + if ( u != null) { + return u; + } + } + } + return null; + } + public static List getChildren(Tree t) { return org.antlr.v4.runtime.tree.Trees.getChildren(t); } @@ -165,7 +212,7 @@ private static List getDescendantsWithFilter(ParseTree parent, ParseT * Пример: * BSLParserRuleContext parent = Trees.getAncestorByRuleIndex(ctx, BSLParser.RULE_statement); */ - @Nullable + @CheckForNull public static BSLParserRuleContext getAncestorByRuleIndex(BSLParserRuleContext element, int type) { var parent = element.getParent(); if (parent == null) { @@ -288,6 +335,26 @@ public static BSLParserRuleContext getRootParent(BSLParserRuleContext tnc) { return tnc; } + /** + * Рекурсивно находит самого верхнего родителя текущей ноды нужного типа + * + * @param context - нода, для которой ищем родителя + * @param index- BSLParser.RULE_* + * @param klass - класс типа + * @param - тип + * @return - если родитель не найден, вернет пустой Optional + */ + public static Optional getRootNode(RuleNode context, int index, Class klass) { + if (klass.isInstance(context)){ + return Optional.of(klass.cast(context)); + } + return Optional.of(context) + .map(BSLParserRuleContext.class::cast) + .map(node -> Trees.getRootParent(node, index)) + .filter(klass::isInstance) + .map(klass::cast); + } + /** * Рекурсивно находит самого верхнего родителя текущей ноды нужного типа * @@ -295,7 +362,7 @@ public static BSLParserRuleContext getRootParent(BSLParserRuleContext tnc) { * @param ruleindex - BSLParser.RULE_* * @return tnc - если родитель не найден, вернет null */ - @Nullable + @CheckForNull public static BSLParserRuleContext getRootParent(BSLParserRuleContext tnc, int ruleindex) { final var parent = tnc.getParent(); if (parent == null) { @@ -316,7 +383,7 @@ public static BSLParserRuleContext getRootParent(BSLParserRuleContext tnc, int r * @param indexes - Collection of BSLParser.RULE_* * @return tnc - если родитель не найден, вернет null */ - @Nullable + @CheckForNull public static BSLParserRuleContext getRootParent(BSLParserRuleContext tnc, Collection indexes) { final var parent = tnc.getParent(); if (parent == null) { @@ -417,7 +484,7 @@ public static boolean nodeContains(ParseTree t, ParseTree exclude, Integer... in } /** - * Получение ноды в дереве по позиции в документе. + * Получение терминальной ноды в дереве по позиции в документе. * * @param tree - дерево, в котором ищем * @param position - искомая позиция @@ -456,6 +523,48 @@ public static Optional findTerminalNodeContainsPosition(BSLParserR return Optional.empty(); } + /** + * Получение ноды в дереве по позиции в документе. + * + * @param tree - дерево, в котором ищем + * @param position - искомая позиция + * @return нода на указанной позиции, если есть + */ + public static Optional findContextContainsPosition(BSLParserRuleContext tree, Position position) { + if (!nodeContainsPosition(tree, position)) { + return Optional.empty(); + } + return Optional.ofNullable(findContextContainsPositionInner(tree, position)); + } + + private static @Nullable BSLParserRuleContext findContextContainsPositionInner(BSLParserRuleContext tree, Position position) { + + var children = Trees.getChildren(tree); + + var isOneElemSize = children.size() == 1; + for (Tree child : children) { + if ((child instanceof TerminalNode) + || (!isOneElemSize && !nodeContainsPosition((BSLParserRuleContext) child, position))) { + continue; + } + BSLParserRuleContext node = findContextContainsPositionInner((BSLParserRuleContext) child, position); + if (node != null) { + return node; + } + } + return tree; + } + + private static boolean nodeContainsPosition(BSLParserRuleContext tree, Position position) { + var start = tree.getStart(); + var stop = tree.getStop(); + if (start == null || stop == null){ + return false; + } + + return positionIsAfterOrOnToken(position, start) && positionIsBeforeOrOnToken(position, stop); + } + /** * @param tokens - список токенов из DocumentContext * @param token - токен, на строке которого требуется найти висячий комментарий 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 25601dee8b8..cdd033fca84 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 @@ -986,6 +986,16 @@ "title": "Using a logical \"OR\" in the \"WHERE\" section of a query", "$id": "#/definitions/LogicalOrInTheWhereSectionOfQuery" }, + "LostVariable": { + "description": "Lost variable", + "default": true, + "type": [ + "boolean", + "object" + ], + "title": "Lost variable", + "$id": "#/definitions/LostVariable" + }, "MagicDate": { "description": "Magic dates", "default": true, diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic_en.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic_en.properties new file mode 100644 index 00000000000..b04e071cfa9 --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic_en.properties @@ -0,0 +1,4 @@ +diagnosticMessage=The value of variable <%s> is not used, the variable is rewritten +diagnosticName=Lost variable + +unusedAfterMessage=The value of variable <%s> is not used further diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic_ru.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic_ru.properties new file mode 100644 index 00000000000..72f6a46da46 --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnostic_ru.properties @@ -0,0 +1,4 @@ +diagnosticMessage=Значение переменной <%s> не используется, переменная перезаписывается дальше по коду +diagnosticName=Потерянная переменная + +unusedAfterMessage=Значение переменной <%s> не используется далее diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnosticTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnosticTest.java new file mode 100644 index 00000000000..a92ab25069e --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/LostVariableDiagnosticTest.java @@ -0,0 +1,94 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2022 + * Alexey Sosnoviy , Nikita Fedkin 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.CleanupContextBeforeClassAndAfterClass; +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; + +@CleanupContextBeforeClassAndAfterClass +class LostVariableDiagnosticTest extends AbstractDiagnosticTest { + LostVariableDiagnosticTest() { + super(LostVariableDiagnostic.class); + } + + @Test + void test() { + + List diagnostics = getDiagnostics(); + + assertThat(diagnostics, true) + .hasMessageOnRange(getMessage("Значение"), 2, 4, 12) + .hasMessageOnRange(getMessageUnused("Значение"), 3, 4, 12) + .hasMessageOnRange(getMessage("МояПеременная"), 4, 4, 17) + .hasMessageOnRange(getMessageUnused("МояПеременная"), 5, 4, 17) + .hasMessageOnRange(getMessage("ТекстЗапроса"), 9, 4, 16) + .hasMessageOnRange(getMessage("ТекстЗапроса"), 23, 4, 16) + .hasMessageOnRange(getMessage("ТекстЗапроса"), 31, 4, 16) + .hasMessageOnRange(getMessage("ТекстЗапроса"), 53, 7, 19) + .hasMessageOnRange(getMessage("ТекстЗапроса"), 69, 2, 14) + .hasMessageOnRange(getMessageUnused("Запрос"), 82, 2, 8) + .hasMessageOnRange(getMessageUnused("Запрос"), 95, 2, 8) + .hasMessageOnRange(getMessage("ТекстЗапроса"), 99, 2, 14) + .hasMessageOnRange(getMessageUnused("ТекстЗапроса"), 101, 6, 18) + .hasMessageOnRange(getMessage("Файл"), 111, 4, 8) + .hasMessageOnRange(getMessageUnused("Файл"), 116, 4, 8) + .hasMessageOnRange(getMessageUnused("ЛокальнаяПеременная"), 127, 8, 27) + .hasMessageOnRange(getMessage("Комментарий"), 139, 4, 15) + .hasMessageOnRange(getMessageUnused("Комментарий"), 139, 21, 32) + .hasMessageOnRange(getMessage("ВидПрава"), 159, 4, 12) + .hasMessageOnRange(getMessageUnused("ВидПрава"), 160, 4, 12) + .hasMessageOnRange(getMessage("НовыйПереход"), 163, 4, 16) + .hasMessageOnRange(getMessageUnused("НовыйПереход"), 164, 4, 16) + .hasMessageOnRange(getMessage("ЭтоОшибкаБлокировки"), 188, 8, 27) + .hasMessageOnRange(getMessageUnused("ЭтоОшибкаБлокировки"), 210, 8, 27) + .hasMessageOnRange(getMessageUnused("мСохраненныйДок"), 243, 4, 19) + .hasMessageOnRange(getMessage("Представление"), 254, 4, 17) + .hasMessageOnRange(getMessage("ЛишниеТэги"), 275, 8, 18) + .hasMessageOnRange(getMessageUnused("ЛишниеТэги"), 276, 8, 18) + .hasMessageOnRange(getMessage("ТекстЗапроса"), 297, 4, 16) + .hasMessageOnRange(getMessage("ЗначениеМодуля"), 305, 4, 18) + .hasMessageOnRange(getMessage("ТекстЗапроса"), 311, 4, 16) + .hasMessageOnRange(getMessageUnused("ТекстЗапроса"), 314, 4, 16) + .hasMessageOnRange(getMessage("ЗначениеМодуля"), 318, 4, 18) + //.hasMessageOnRange(getMessageUnused("Значение"), 329, 8, 16) // TODO не ошибка + .hasMessageOnRange(getMessage("Элем22"), 335, 16, 22) + .hasMessageOnRange(getMessage("Значение23"), 344, 12, 22) + .hasMessageOnRange(getMessageUnused("Значение23"), 345, 12, 22) + .hasMessageOnRange(getMessage("Значение24"), 354, 12, 22) + .hasMessageOnRange(getMessage("ТекстЗапросаВБлоке"), 376, 0, 18) + .hasMessageOnRange(getMessage("ЗначениеМодуля"), 380, 0, 14) + .hasSize(37); + } + + String getMessage(String name){ + return String.format("Значение переменной <%s> не используется, переменная перезаписывается дальше по коду", name); + } + + String getMessageUnused(String name){ + return String.format("Значение переменной <%s> не используется далее", name); + } +} diff --git a/src/test/resources/diagnostics/LostVariableDiagnostic.bsl b/src/test/resources/diagnostics/LostVariableDiagnostic.bsl new file mode 100644 index 00000000000..a6e738752d7 --- /dev/null +++ b/src/test/resources/diagnostics/LostVariableDiagnostic.bsl @@ -0,0 +1,383 @@ +Перем ЗначениеМодуля; +Процедура Тест1() + Значение = 1; // ошибка + Значение = 2; // ошибка + МояПеременная = СтрЗаменить(КакаятоПеременная); // ошибка + МояПеременная = СтрЗаменить(КакаятоДругаяПеременная); // ошибка +КонецПроцедуры + +Процедура Тест2() + ТекстЗапроса = "Первый"; // ошибка + ТекстЗапроса = "Второй"; + Запрос = Новый Запрос(ТекстЗапроса); +КонецПроцедуры + +Процедура Тест3() + ТекстЗапроса = "Первый"; // нет ошибки + Если Условие Тогда + ТекстЗапроса = "Второй"; + КонецЕсли; + Запрос = Новый Запрос(ТекстЗапроса); +КонецПроцедуры + +Процедура Тест4() + ТекстЗапроса = "Первый"; // ошибка + Если Условие Тогда + ТекстЗапроса = "Второй"; + Запрос = Новый Запрос(ТекстЗапроса); + КонецЕсли; +КонецПроцедуры + +Процедура Тест5() + ТекстЗапроса = "Первый"; // ошибка + Если Условие Тогда + ТекстЗапроса = "Второй"; + Если Условие Тогда + Запрос = Новый Запрос(ТекстЗапроса); + КонецЕсли; + КонецЕсли; +КонецПроцедуры + +Процедура Тест6() + Если Условие Тогда + ТекстЗапроса = "Первый"; // не ошибка + Иначе + ТекстЗапроса = "Второй"; + КонецЕсли; + Запрос = Новый Запрос(ТекстЗапроса); +КонецПроцедуры + +Процедура Тест7() + Если Условие Тогда + ТекстЗапроса = "Первый"; // не ошибка + Иначе + ТекстЗапроса = "Второй"; // ошибка + ТекстЗапроса = "Третий"; + КонецЕсли; + Запрос = Новый Запрос(ТекстЗапроса); +КонецПроцедуры + +Процедура Тест9() + Попытка + ТекстЗапроса = "Первый"; // не ошибка + Исключение + ТекстЗапроса = "Второй"; + КонецПопытки; + Запрос = Новый Запрос(ТекстЗапроса); +КонецПроцедуры + +Процедура Тест10() + ТекстЗапроса = "Первый"; // ошибка + ТекстЗапроса = "Второй"; + Если Условие Тогда + Запрос = Новый Запрос(ТекстЗапроса); + КонецЕсли; +КонецПроцедуры + +Процедура Тест11() + ТекстЗапроса = "Первый"; //не ошибка + Если Условие Тогда + ТекстЗапроса = "Второй";// не ошибка + Запрос = Новый Запрос(ТекстЗапроса); + КонецЕсли; + Запрос = Новый Запрос(ТекстЗапроса); // ошибка +КонецПроцедуры + +Процедура Тест12() + ТекстЗапроса = "Первый"; //не ошибка + Если Условие Тогда + ТекстЗапроса = "Второй";// не ошибка + Если Условие2 Тогда + ТекстЗапроса = "Третий";// не ошибка + Запрос = Новый Запрос(ТекстЗапроса); + КонецЕсли; + //Запрос = Новый Запрос(ТекстЗапроса); + КонецЕсли; + Запрос = Новый Запрос(ТекстЗапроса); // ошибка +КонецПроцедуры + +Процедура Тест13() + ТекстЗапроса = "Первый"; // ошибка + Если Условие Тогда + ТекстЗапроса = "Второй"; // ошибка + КонецЕсли; +КонецПроцедуры + +Процедура ПолучитьУжеСуществующиеСнипетыИзОбработок() + Файл = Новый Файл(КаталогФич); // не ошибка + Если Не Файл.Существует() Тогда + Возврат; + КонецЕсли; + + Файл = Новый Файл(КаталогФич); // ошибка + БылиОшибки = Ложь; + НачальныйКаталог = КаталогФич; + КаталогПоиска = НачальныйКаталог; + + Файл = Новый Файл(НачальныйКаталог); // ошибка +КонецПроцедуры + +//Или другой пример правильного кода (изменение происходит только при определённых условиях): +Процедура ПерезаписьВУсловии() + ЛокальнаяПеременная = ВызовМоейФункции(); // не ошибка + Если Условие Тогда + ЛокальнаяПеременная = 42; + ИначеЕсли ДругоеУсловие Тогда + ЛокальнаяПеременная = ВызовФункции(); + Иначе + ЛокальнаяПеременная = 0; // ошибка + КонецЕсли; +КонецПроцедуры + +Процедура Тест14() + ДобавляемыйНомер = 1; + Пока Условие() Цикл + ДобавляемыйНомер = ДобавляемыйНомер + 1; // не ошибка + КонецЦикла; +КонецПроцедуры + +Функция ВыраженияНаОднойСтроке() + Комментарий = 10;Комментарий = 20; // сразу 2 ошибки (важно, что нет пробела после 10;) + + Возврат Неопределено; +КонецФункции + +Функция РезультатВыполненияПриПеренаправлении(Знач ЗадачаСсылка) + // в этом методе нет ошибок + Комментарий = "Текст"; + Комментарий = ?(ПустаяСтрока(Комментарий), "", Комментарий + Символы.ПС); + + ТекстОповещения = "Текст"; + ТекстОповещения = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(ТекстОповещения, Формат); + + ТекстЗапроса = "Текст"; + ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "Текст", ДругойТекстЗапроса); + + Возврат Неопределено; +КонецФункции + +Процедура Тест15() + ВидПрава = ВидыПрав.Добавить(); // ошибка + ВидПрава = ВидыПрав.Добавить(); // ошибка + + // ошибка + НовыйПереход = ТаблицаПереходовНоваяСтрока("Начало", "СтраницаНавигацииНачало"); // нет ошибок + НовыйПереход = ТаблицаПереходовНоваяСтрока("НастройкаВыгрузки", "СтраницаНавигацииПродолжение"); +КонецПроцедуры + +Процедура Тест16() + // в этом методе нет ошибок + КоличествоОбработанных = ПорцияЭлементовДанных.Количество(); + НачатьТранзакцию(); + Попытка + Блокировка.Заблокировать(); + ЗафиксироватьТранзакцию(); + Исключение + ОтменитьТранзакцию(); + КоличествоОбработанных = 0; // не ошибка + КонецПопытки; + ПорцияЭлементовДанных = Неопределено; + + ТребуетсяПрерватьОбработкуЭлементов(ПараметрыОбновления, КоличествоОбработанных); +КонецПроцедуры + +Процедура УстановитьПометкуУдаленияДляОбъектов() + Блокировка = Новый БлокировкаДанных; + + НачатьТранзакцию(); + Попытка + ЭтоОшибкаБлокировки = Истина; // ошибка. а вот кейс ниже - не ошибка + Блокировка.Заблокировать(); + + ЭтоОшибкаБлокировки = Ложь; + ЗафиксироватьТранзакцию(); + Исключение + ОтменитьТранзакцию(); + Если ЭтоОшибкаБлокировки Тогда + КонецЕсли; + КонецПопытки; +КонецПроцедуры + +Процедура ПереустановкаВБлокеИсключения() + Блокировка = Новый БлокировкаДанных; + + НачатьТранзакцию(); + Попытка + ЭтоОшибкаБлокировки = Истина; // не ошибка + Блокировка.Заблокировать(); + ЗафиксироватьТранзакцию(); + Исключение + ОтменитьТранзакцию(); + ЭтоОшибкаБлокировки = Ложь; // ошибка + КонецПопытки; +КонецПроцедуры + +Процедура ОперативнаяПамятьДоступнаяКлиентскомуПриложению() + // в этом методе нет ошибок + ДоступныйОбъем = 0; +#Если Сервер Или ТолстыйКлиентОбычноеПриложение Тогда + ДоступныйОбъем = 1; +#ИначеЕсли ТонкийКлиент Тогда + ДоступныйОбъем = 2; +#Иначе + ДоступныйОбъем = 0; +#КонецЕсли + Возврат ДоступныйОбъем; +КонецПроцедуры + +Процедура ПодготовитьТабличныйДокумент() + ТабличныйДокумент.Вывести(Отступ); + Для НомерЯчейки = 1 По 2 Цикл // не ошибка - другое правило про итератор + ТабличныйДокумент.Присоединить(ПустаяЯчейка); + КонецЦикла; + + Для НомерЯчейки = 1 По КоличествоЯчеекДляЗаполнения - 2 Цикл // не ошибка + ПечатьСтроки(ЗначениеЗаполнения); + КонецЦикла; +КонецПроцедуры + +Процедура Событие(Параметры, Отказ) + мСохраненныйДок = Неопределено; // не ошибка + Если Параметры.Свойство("АвтоТест") Тогда + Возврат; + КонецЕсли; + мСохраненныйДок = Параметры.ЗначениеКопирования; // ошибка +КонецПроцедуры + +&НаКлиенте +Процедура ПредставлениеОтбораПоПериодуНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка) + СтандартнаяОбработка = Ложь; // не ошибка +КонецПроцедуры + +&НаСервереБезКонтекста +Функция ПредставлениеЧастиАдреса_ЕДТ(АдресСтруктура, СписокПолей) + + Представление = ""; // ошибка + СтруктураПолей = Новый Структура(СписокПолей); + ЗаполнитьЗначенияСвойств(СтруктураПолей, АдресСтруктура); + + МодульУправлениеКонтактнойИнформацией = ОбщегоНазначения.ОбщийМодуль("УправлениеКонтактнойИнформацией"); + Представление = МодульУправлениеКонтактнойИнформацией.ПредставлениеКонтактнойИнформации(СтруктураПолей); + + Возврат Представление; + +КонецФункции + +&НаСервере +Функция ЗаполнитьТэгиОбъектов(РеквизитИмяОбъекта, ОбъектыОбработчика, РазметкаКода, ВнешниеТэги = Неопределено, СортироватьПоТэгам = Истина) + + ЭтоИмяПроцедуры = РеквизитИмяОбъекта = "Процедура" ИЛИ РеквизитИмяОбъекта = "Процедура2"; + ТэгиМодуля = РазметкаКода.Тэги.ПоМодулям[РазметкаКода.ИмяМодуля]; + Если ОбъектыОбработчика.Колонки.Найти("Тэги") = Неопределено Тогда + ОбъектыОбработчика.Колонки.Добавить("Тэги", Новый ОписаниеТипов("Строка",,Новый КвалификаторыСтроки(100))); + КонецЕсли; + ЕстьТэгиМодуляОбновления = ОбъектыОбработчика.Колонки.Найти("ТэгиМодуляОбновления") <> Неопределено; + Если ТэгиМодуля = Неопределено Тогда + ЛишниеТэги = Новый Массив; // не ошибка, если включен флаг Игнорировать типизацию для ЕДТ + ЛишниеТэги = ЛишниеТэгиВОбласти(ВнешниеТэги, РазметкаКода); // ошибка + + Возврат ОбъектыОбработчика; + КонецЕсли; + + Возврат ОбъектыОбработчика; +КонецФункции + +Процедура ПереборКоллекции(ТабличныйДокумент) + Для Каждого Элемент Из Коллекция Цикл // не ошибка - другое правило про итератор + ТабличныйДокумент.Присоединить(ПустаяЯчейка); + КонецЦикла; + + Для Каждого Элемент Из Коллекция Цикл // не ошибка - другое правило про итератор + ПечатьСтроки(ЗначениеЗаполнения); + КонецЦикла; +КонецПроцедуры + +Процедура Тест17() + #Область Первая + #Область Вторая + ТекстЗапроса = "Первый"; // ошибка + ТекстЗапроса = "Второй"; + Запрос = Новый Запрос(ТекстЗапроса); + #КонецОбласти + #КонецОбласти +КонецПроцедуры + +Процедура Тест18() + ЗначениеМодуля = "Первый"; // ошибка + ЗначениеМодуля = "Второй"; + Запрос = Новый Запрос(ЗначениеМодуля); +КонецПроцедуры + +Процедура Тест19() + ТекстЗапроса = "Первый"; // ошибка + ТекстЗапроса = "Второй"; + Запрос = Новый Запрос(ТекстЗапроса); + ТекстЗапроса = "Третий"; // также ошибка +КонецПроцедуры + +Процедура Тест20() + ЗначениеМодуля = "Первый"; // ошибка + ЗначениеМодуля = "Второй"; + Запрос = Новый Запрос(ЗначениеМодуля); + ЗначениеМодуля = "Третий"; // не ошибка +КонецПроцедуры + +Процедура Тест21() + Значение21 = 10; // не ошибка + Для Каждого Элем ИЗ Коллекция Цикл + //Если ДругоеУсловие() Тогда + Элем.Реквизит = Вычисление(Значение21); + Значение21 = 20; // не ошибка, есть использование + //КонецЕсли; + КонецЦикла; +КонецПроцедуры + +Процедура Тест22() + Для Каждого Элем22 ИЗ Коллекция1 Цикл // ошибка + Для Каждого Элем22 ИЗ Коллекция2 Цикл // не ошибка - есть правило про итератор + КонецЦикла; + КонецЦикла; +КонецПроцедуры + +Процедура Тест23() + Пока Условие() Цикл + Если ДругоеУсловие() Тогда + Значение23 = 10; // ошибка + Значение23 = 20; // ошибка + КонецЕсли; + КонецЦикла; +КонецПроцедуры + +Процедура Тест24() + Значение24 = 10; // не ошибка + Пока Условие() Цикл + Если ДругоеУсловие() Тогда + Значение24 = 20; // ошибка, нет использования далее + КонецЕсли; + КонецЦикла; +КонецПроцедуры + +Процедура Тест25() + Значение25 = 10; // не ошибка + Пока Условие() Цикл + Значение25 = 20; // не ошибка, есть использование далее + КонецЦикла; + Метод(Значение25); +КонецПроцедуры + +Процедура Тест26() + ЗначениеМодуля = 10; // не ошибка + Пока Условие() Цикл + Если ДругоеУсловие() Тогда + ЗначениеМодуля = 20; // не ошибка для значения модуля + КонецЕсли; + КонецЦикла; +КонецПроцедуры + +ТекстЗапросаВБлоке = "Первый"; // ошибка +ТекстЗапросаВБлоке = "Второй"; +Запрос = Новый Запрос(ТекстЗапросаВБлоке); + +ЗначениеМодуля = "Первый"; // ошибка +ЗначениеМодуля = "Второй"; +Запрос1 = Новый Запрос(ЗначениеМодуля);