-
Notifications
You must be signed in to change notification settings - Fork 139
feat: new diagnostic SuspiciousChangeAndValidate #4398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pravets
wants to merge
3
commits into
1c-syntax:develop
Choose a base branch
from
pravets:feat/new-diagnostic-suspicious-change-and-validate
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Подозрительное использование &ИзменениеИКонтроль (SuspiciousChangeAndValidate) | ||
|
|
||
| <!-- Блоки выше заполняются автоматически, не трогать --> | ||
| ## Описание диагностики | ||
|
|
||
| Метод, помеченный аннотацией `&ИзменениеИКонтроль`, содержит одновременно директивы `#Удаление` / `#КонецУдаления` и `#Вставка` / `#КонецВставки`. Такая комбинация означает полную замену тела метода, что скрывает реальные изменения от ревьюера. | ||
|
|
||
| Аннотация `&ИзменениеИКонтроль` предназначена для точечных правок. Для полной замены метода используйте `&Вместо`. | ||
|
|
||
| ## Примеры | ||
|
|
||
| ### Неправильно | ||
|
|
||
| ```bsl | ||
| &ИзменениеИКонтроль("МетодИзКонфигурации") | ||
| Процедура префМетодИзКонфигурации() | ||
|
|
||
| #Удаление | ||
| старый код | ||
| #КонецУдаления | ||
| #Вставка | ||
| новый код | ||
| #КонецВставки | ||
|
|
||
| КонецПроцедуры | ||
| ``` | ||
|
|
||
| ### Правильно | ||
|
|
||
| ```bsl | ||
| // Точечные изменения | ||
| &ИзменениеИКонтроль("МетодИзКонфигурации") | ||
| Процедура префМетодИзКонфигурации() | ||
| ... // оригинальный код | ||
|
|
||
| #Удаление | ||
| проблемная строка | ||
| #КонецУдаления | ||
| #Вставка | ||
| исправленная строка | ||
| #КонецВставки | ||
|
|
||
| ... // остальной код | ||
| КонецПроцедуры | ||
|
|
||
| // Или полная замена через &Вместо | ||
| &Вместо("МетодИзКонфигурации") | ||
| Процедура префМетодИзКонфигурации() | ||
| новый код | ||
| КонецПроцедуры | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Suspicious use of &ChangeAndValidate (SuspiciousChangeAndValidate) | ||
|
|
||
| <!-- Блоки выше заполняются автоматически, не трогать --> | ||
| ## Diagnostic description | ||
|
|
||
| A method annotated with `&ChangeAndValidate` contains both `#Delete` / `#EndDelete` and `#Insert` / `#EndInsert` directives, indicating full method body replacement that hides changes from reviewers. | ||
|
|
||
| `&ChangeAndValidate` is designed for targeted modifications. For full replacement, use `&Instead`. | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Incorrect | ||
|
|
||
| ```bsl | ||
| &ChangeAndValidate("ConfigurationMethod") | ||
| Procedure prefConfigurationMethod() | ||
|
|
||
| #Delete | ||
| old code | ||
| #EndDelete | ||
| #Insert | ||
| new code | ||
| #EndInsert | ||
|
|
||
| EndProcedure | ||
| ``` | ||
|
|
||
| ### Correct | ||
|
|
||
| ```bsl | ||
| // Targeted changes | ||
| &ChangeAndValidate("ConfigurationMethod") | ||
| Procedure prefConfigurationMethod() | ||
| ... // original code | ||
|
|
||
| #Delete | ||
| problematic line | ||
| #EndDelete | ||
| #Insert | ||
| fixed line | ||
| #EndInsert | ||
|
|
||
| ... // rest of code | ||
| EndProcedure | ||
|
|
||
| // Or full replacement via &Instead | ||
| &Instead("ConfigurationMethod") | ||
| Procedure prefConfigurationMethod() | ||
| new code | ||
| EndProcedure | ||
| ``` |
170 changes: 170 additions & 0 deletions
170
...thub/_1c_syntax/bsl/languageserver/diagnostics/SuspiciousChangeAndValidateDiagnostic.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| /* | ||
| * This file is a part of BSL Language Server. | ||
| * | ||
| * Copyright (c) 2018-2026 | ||
| * Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> 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.annotations.Annotation; | ||
| import com.github._1c_syntax.bsl.languageserver.context.symbol.annotations.AnnotationKind; | ||
| 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.utils.Ranges; | ||
| import com.github._1c_syntax.bsl.parser.BSLLexer; | ||
| import org.antlr.v4.runtime.Token; | ||
| import org.eclipse.lsp4j.Position; | ||
| import org.eclipse.lsp4j.Range; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| /** | ||
| * Метод с аннотацией {@code &ИзменениеИКонтроль}, в котором директивы | ||
| * {@code #Удаление} и {@code #Вставка} полностью заменяют тело метода. | ||
| * <p> | ||
| * Полная замена — когда оригинального кода не остаётся ни до, ни между, | ||
| * ни после блоков удаления/вставки. Для такого сценария следует | ||
| * использовать аннотацию {@code &Вместо}. | ||
| * <p> | ||
| * Проверяет наличие обеих пар маркеров | ||
| * ({@code #Удаление}+{@code #КонецУдаления}, | ||
| * {@code #Вставка}+{@code #КонецВставки}), их положение у границ метода | ||
| * и отсутствие кода между блоками удаления и вставки. | ||
| * | ||
| * @see <a href="https://its.1c.ru/db/v8std/content/455/hdoc">Стандарт 455</a> | ||
| */ | ||
| @DiagnosticMetadata( | ||
| type = DiagnosticType.CODE_SMELL, | ||
| severity = DiagnosticSeverity.MAJOR, | ||
| scope = DiagnosticScope.BSL, | ||
| minutesToFix = 5, | ||
| tags = { | ||
| DiagnosticTag.BADPRACTICE, | ||
| DiagnosticTag.SUSPICIOUS | ||
| } | ||
| ) | ||
| public class SuspiciousChangeAndValidateDiagnostic extends AbstractDiagnostic { | ||
|
|
||
| private static final int PROXIMITY_LINES = 3; | ||
|
|
||
| @Override | ||
| public void check() { | ||
| var tokens = documentContext.getTokens(); | ||
|
|
||
| documentContext.getSymbolTree().getMethods() | ||
| .stream() | ||
| .filter(method -> method.getAnnotations().stream() | ||
| .map(Annotation::getKind) | ||
| .anyMatch(kind -> kind == AnnotationKind.CHANGEANDVALIDATE)) | ||
| .filter(method -> isFullReplacement(method, tokens)) | ||
| .forEach(method -> | ||
| diagnosticStorage.addDiagnostic(method.getSubNameRange(), | ||
| info.getMessage(method.getName()))); | ||
| } | ||
|
|
||
| /** | ||
| * Полная замена: обе пары маркеров присутствуют, | ||
| * {@code #Удаление} у начала метода, {@code #КонецВставки} у конца, | ||
| * и между {@code #КонецУдаления} и {@code #Вставка} нет кода. | ||
| */ | ||
| private static boolean isFullReplacement(MethodSymbol method, List<Token> tokens) { | ||
| var range = method.getRange(); | ||
| int methodStart = range.getStart().getLine(); | ||
| int methodEnd = range.getEnd().getLine(); | ||
|
|
||
| var methodTokens = tokensInRange(tokens, range); | ||
|
|
||
| // Требуем обе пары маркеров | ||
| if (!hasPair(methodTokens, BSLLexer.PREPROC_DELETE, BSLLexer.PREPROC_ENDDELETE) | ||
| || !hasPair(methodTokens, BSLLexer.PREPROC_INSERT, BSLLexer.PREPROC_ENDINSERT)) { | ||
| return false; | ||
| } | ||
|
|
||
| int firstDelete = firstLineOf(methodTokens, BSLLexer.PREPROC_DELETE); | ||
| int lastEndInsert = lastLineOf(methodTokens, BSLLexer.PREPROC_ENDINSERT); | ||
|
|
||
| // Блоки должны быть у границ метода | ||
| if (firstDelete < 0 || lastEndInsert < 0) { | ||
| return false; | ||
| } | ||
| if ((firstDelete - methodStart) > PROXIMITY_LINES) { | ||
| return false; | ||
| } | ||
| if ((methodEnd - lastEndInsert) > PROXIMITY_LINES) { | ||
| return false; | ||
| } | ||
|
|
||
| // Между концом удаления и началом вставки не должно быть кода | ||
| int endDelete = lastLineOf(methodTokens, BSLLexer.PREPROC_ENDDELETE); | ||
| int startInsert = firstLineOf(methodTokens, BSLLexer.PREPROC_INSERT); | ||
| if (endDelete < 0 || startInsert < 0) { | ||
| return false; | ||
| } | ||
|
|
||
| return !hasCodeBetween(tokens, endDelete, startInsert); | ||
| } | ||
|
|
||
| private static boolean hasPair(List<Token> tokens, int startType, int endType) { | ||
| return containsToken(tokens, startType) && containsToken(tokens, endType); | ||
| } | ||
|
|
||
| private static boolean containsToken(List<Token> tokens, int type) { | ||
| return tokens.stream().anyMatch(t -> t.getType() == type); | ||
| } | ||
|
|
||
| /** | ||
| * Есть ли код (default-channel токены) между строками {@code fromLine} | ||
| * и {@code toLine} включительно. Игнорируем сами маркеры. | ||
| */ | ||
| private static boolean hasCodeBetween(List<Token> tokens, int fromLine, int toLine) { | ||
| return tokens.stream() | ||
| .filter(t -> t.getChannel() == Token.DEFAULT_CHANNEL) | ||
| .anyMatch(t -> { | ||
| int line = t.getLine() - 1; | ||
| return line >= fromLine && line <= toLine | ||
| && t.getType() != BSLLexer.PREPROC_ENDDELETE | ||
| && t.getType() != BSLLexer.PREPROC_INSERT; | ||
| }); | ||
| } | ||
|
|
||
| private static List<Token> tokensInRange(List<Token> tokens, Range range) { | ||
| return tokens.stream() | ||
| .filter(token -> Ranges.containsPosition(range, | ||
| new Position(token.getLine() - 1, 0))) | ||
| .toList(); | ||
| } | ||
|
|
||
| private static int firstLineOf(List<Token> tokens, int type) { | ||
| return tokens.stream() | ||
| .filter(t -> t.getType() == type) | ||
| .mapToInt(t -> t.getLine() - 1) | ||
| .min().orElse(-1); | ||
| } | ||
|
|
||
| private static int lastLineOf(List<Token> tokens, int type) { | ||
| return tokens.stream() | ||
| .filter(t -> t.getType() == type) | ||
| .mapToInt(t -> t.getLine() - 1) | ||
| .max().orElse(-1); | ||
| } | ||
|
|
||
| } | ||
2 changes: 2 additions & 0 deletions
2
...syntax/bsl/languageserver/diagnostics/SuspiciousChangeAndValidateDiagnostic_en.properties
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| diagnosticMessage=Method "%s" uses &ChangeAndValidate for full code replacement: both #Delete and #Insert detected. Use &Instead | ||
| diagnosticName=Suspicious use of &ChangeAndValidate |
2 changes: 2 additions & 0 deletions
2
...syntax/bsl/languageserver/diagnostics/SuspiciousChangeAndValidateDiagnostic_ru.properties
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| diagnosticMessage=Метод "%s" использует &ИзменениеИКонтроль для полной замены кода: обнаружены #Удаление и #Вставка. Используйте &Вместо | ||
| diagnosticName=Подозрительное использование &ИзменениеИКонтроль |
54 changes: 54 additions & 0 deletions
54
.../_1c_syntax/bsl/languageserver/diagnostics/SuspiciousChangeAndValidateDiagnosticTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| /* | ||
| * This file is a part of BSL Language Server. | ||
| * | ||
| * Copyright (c) 2018-2026 | ||
| * Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> 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 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 SuspiciousChangeAndValidateDiagnosticTest | ||
| extends AbstractDiagnosticTest<SuspiciousChangeAndValidateDiagnostic> { | ||
|
|
||
| SuspiciousChangeAndValidateDiagnosticTest() { | ||
| super(SuspiciousChangeAndValidateDiagnostic.class); | ||
| } | ||
|
|
||
| @Test | ||
| void detectsFullReplacementWithDeleteAndInsert() { | ||
| List<Diagnostic> diagnostics = getDiagnostics(); | ||
|
|
||
| // СИзменением: #Удаление в начале + #КонецВставки в конце, кода между | ||
| // блоками нет → срабатывает (1) | ||
| // БезИзменения: нет &ИзменениеИКонтроль → не срабатывает | ||
| // ФункцияСИзменением: оригинальный код до #Удаления → не срабатывает | ||
| // ПустаяПроцедура: нет директив → не срабатывает | ||
| // НеполнаяПара: нет #КонецУдаления → не срабатывает | ||
| // СКодомМеждуБлоками: оригинальный код между #КонецУдаления и | ||
| // #Вставка → не срабатывает | ||
| assertThat(diagnostics).hasSize(1); | ||
| assertThat(diagnostics, true) | ||
| .hasRange(1, 10, 1, 21); // СИзменением | ||
| } | ||
| } |
63 changes: 63 additions & 0 deletions
63
src/test/resources/diagnostics/SuspiciousChangeAndValidateDiagnostic.bsl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| &ИзменениеИКонтроль | ||
| Процедура СИзменением() | ||
|
|
||
| #Удаление | ||
| старый код | ||
| #КонецУдаления | ||
| #Вставка | ||
| // какой-то код | ||
| #КонецВставки | ||
|
|
||
| КонецПроцедуры | ||
|
|
||
| Процедура БезИзменения() | ||
|
|
||
| #Вставка | ||
| // какой-то код | ||
| #КонецВставки | ||
|
|
||
| КонецПроцедуры | ||
|
|
||
| &ИзменениеИКонтроль | ||
| Функция ФункцияСИзменением() | ||
|
|
||
| // оригинальный код | ||
|
|
||
| #Удаление | ||
| удаляемый блок | ||
| #КонецУдаления | ||
| #Вставка | ||
| // код | ||
| #КонецВставки | ||
|
|
||
| КонецФункции | ||
|
|
||
| Процедура ПустаяПроцедура() | ||
| // нет вставки | ||
| КонецПроцедуры | ||
|
|
||
| &ИзменениеИКонтроль | ||
| Процедура НеполнаяПара() | ||
|
|
||
| #Удаление | ||
| старый код | ||
| #Вставка | ||
| // код | ||
| #КонецВставки | ||
|
|
||
| КонецПроцедуры | ||
|
|
||
| &ИзменениеИКонтроль | ||
| Процедура СКодомМеждуБлоками() | ||
|
|
||
| #Удаление | ||
| старый код | ||
| #КонецУдаления | ||
|
|
||
| ОбработчикМеждуБлоками(); | ||
|
|
||
| #Вставка | ||
| // код | ||
| #КонецВставки | ||
|
|
||
| КонецПроцедуры |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Prove that the blocks cover the method body.
The current condition checks only the first deletion line and the last insertion-end line. It still reports a method that keeps original code between those blocks. That is a targeted change, not a full replacement. Match the paired intervals and verify that no original code remains outside them.
Also applies to: 102-109
🤖 Prompt for AI Agents