Skip to content

Commit

Permalink
feat(diagnostic): Новое правило "Обращение к отсутствующему методу об…
Browse files Browse the repository at this point in the history
…щего модуля MissingCommonModuleMethod "- ГОТОВО (#2827)

* Реализация правила

* Обращение к приватным методам

* Исключил ФП параметры с именами общих модулей

* переименовал правило

* документация + настройка правила

* добавил тег правила

* уточнил сообщения правила

* использован символьный репозиторий

вместо работы с аст-деревом

* @CleanupContextBeforeClassAndAfterEachTestMethod

* реализованы недостающие кейсы

* убрал комментарий

* комментарий про приватные методы

исключил срабатывание на внутренних вызовах внутри общих модулей

* Поправил текст сообщения

исправил замечания из ПР

* исправил замечания из ПР

* кейс для покрытия

* уточнил проверку приватных методов

* замечание СонарЛинт
  • Loading branch information
artbear authored Dec 21, 2022
1 parent 829ef80 commit 85d0907
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 0 deletions.
23 changes: 23 additions & 0 deletions docs/diagnostics/MissingCommonModuleMethod.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Обращение к отсутствующему методу общего модуля (MissingCommonModuleMethod)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Описание диагностики
<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->
Правило регистрирует ошибочные обращения к методам общих модулей.
Находятся проблемные варианты
- когда метода нет в указанном общем модуле
- когда метод есть в общем модуле, но метод не является экспортным
- когда у общего модуля отсутствуют исходники, все обращения к любым его методам помечаются как ошибочные

Исключаются варианты
- когда имя переменной совпадает с именем общего модуля
## Примеры
<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->

## Источники
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников
* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->
16 changes: 16 additions & 0 deletions docs/en/diagnostics/MissingCommonModuleMethod.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Referencing a missing common module method (MissingCommonModuleMethod)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Description
<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->

## Examples
<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->

## Sources
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников
* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2022
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.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.references.model.LocationRepository;
import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType;
import com.github._1c_syntax.bsl.languageserver.references.model.SymbolOccurrence;
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
import com.github._1c_syntax.bsl.parser.BSLParserRuleContext;
import com.github._1c_syntax.bsl.types.ConfigurationSource;
import com.github._1c_syntax.bsl.types.ModuleType;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.antlr.v4.runtime.tree.ParseTree;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolKind;

import java.util.Optional;

@DiagnosticMetadata(
type = DiagnosticType.ERROR,
severity = DiagnosticSeverity.BLOCKER,
scope = DiagnosticScope.BSL,
minutesToFix = 5,
tags = {
DiagnosticTag.ERROR
}
)

@RequiredArgsConstructor
public class MissingCommonModuleMethodDiagnostic extends AbstractDiagnostic {
public static final String PRIVATE_METHOD_MESSAGE = "privateMethod";
private final LocationRepository locationRepository;

private static String getMethodNameByLocation(BSLParserRuleContext node, Range range) {
return Trees.findTerminalNodeContainsPosition(node, range.getEnd())
.map(ParseTree::getText)
.orElseThrow();
}

@Override
protected void check() {
if (documentContext.getServerContext().getConfiguration().getConfigurationSource() == ConfigurationSource.EMPTY){
return;
}
locationRepository.getSymbolOccurrencesByLocationUri(documentContext.getUri())
.filter(symbolOccurrence -> symbolOccurrence.getOccurrenceType() == OccurrenceType.REFERENCE)
.filter(symbolOccurrence -> symbolOccurrence.getSymbol().getSymbolKind() == SymbolKind.Method)
.filter(symbolOccurrence -> symbolOccurrence.getSymbol().getModuleType() == ModuleType.CommonModule)
.map(this::getReferenceToMethodCall)
.flatMap(Optional::stream)
.forEach(this::fireIssue);
}

private Optional<CallData> getReferenceToMethodCall(SymbolOccurrence symbolOccurrence) {
final var symbol = symbolOccurrence.getSymbol();
final var document = documentContext.getServerContext()
.getDocument(symbol.getMdoRef(), symbol.getModuleType())
.orElseThrow();
final var mdObject = document.getMdObject().orElseThrow();

// т.к. через refIndex.getReferences нельзя получить приватные методы, приходится обходить символы модуля
final var methodSymbol = document
.getSymbolTree().getMethodSymbol(symbol.getSymbolName());
if (methodSymbol.isEmpty()){
final var location = symbolOccurrence.getLocation();
// Нельзя использовать symbol.getSymbolName(), т.к. имя в нижнем регистре
return Optional.of(
new CallData(mdObject.getName(),
getMethodNameByLocation(documentContext.getAst(), location.getRange()),
location.getRange(), false, false));
}
// вызовы приватных методов внутри самого модуля пропускаем
if (document.getUri().equals(documentContext.getUri())){
return Optional.empty();
}
return methodSymbol
.filter(methodSymbol2 -> !methodSymbol2.isExport())
.map(methodSymbol1 -> new CallData(mdObject.getName(),
methodSymbol1.getName(),
symbolOccurrence.getLocation().getRange(), true, true));
}

private void fireIssue(CallData callData) {
final String message;
if (!callData.exists){
message = info.getMessage(callData.methodName, callData.moduleName);
} else {
message = info.getResourceString(PRIVATE_METHOD_MESSAGE, callData.methodName, callData.moduleName);
}
diagnosticStorage.addDiagnostic(callData.moduleMethodRange, message);
}

@Value
@AllArgsConstructor
private static class CallData {
String moduleName;
String methodName;
Range moduleMethodRange;
boolean nonExport;
boolean exists;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,16 @@
},
"$id": "#/definitions/MissingCodeTryCatchEx"
},
"MissingCommonModuleMethod": {
"description": "Referencing a missing common module method",
"default": true,
"type": [
"boolean",
"object"
],
"title": "Referencing a missing common module method",
"$id": "#/definitions/MissingCommonModuleMethod"
},
"MissingEventSubscriptionHandler": {
"description": "Event subscription handler missing",
"default": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
diagnosticMessage=The method %s of %s common module does not exist
diagnosticName=Referencing a missing common module method

privateMethod=Correct the reference to the non export %s method of the common module %s
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
diagnosticMessage=Метод %s общего модуля %s не существует
diagnosticName=Обращение к отсутствующему методу общего модуля

privateMethod=Исправьте обращение к закрытому, неэкспортному методу %s общего модуля %s
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2022
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> 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.CleanupContextBeforeClassAndAfterEachTestMethod;
import com.github._1c_syntax.utils.Absolute;
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;

@CleanupContextBeforeClassAndAfterEachTestMethod
class MissingCommonModuleMethodDiagnosticTest extends AbstractDiagnosticTest<MissingCommonModuleMethodDiagnostic> {

private static final String PATH_TO_METADATA = "src/test/resources/metadata/designer";

MissingCommonModuleMethodDiagnosticTest() {
super(MissingCommonModuleMethodDiagnostic.class);
}

@Test
void test() {
initServerContext(Absolute.path(PATH_TO_METADATA));

List<Diagnostic> diagnostics = getDiagnostics();

assertThat(diagnostics, true)
.hasMessageOnRange("Метод МетодНесуществующий общего модуля ПервыйОбщийМодуль не существует", 1, 22, 41)
.hasMessageOnRange("Метод ДругойМетодНесуществующий общего модуля ПервыйОбщийМодуль не существует", 2, 26, 51)
.hasMessageOnRange("Метод ЕщеМетодНесуществующий общего модуля ПервыйОбщийМодуль не существует", 3, 22, 44)
.hasMessageOnRange("Метод ЕщеОдинМетодНесуществующий общего модуля ПервыйОбщийМодуль не существует", 4, 22, 48)
.hasMessageOnRange("Метод ЕщеДругойМетодНесуществующий общего модуля ПервыйОбщийМодуль не существует", 5, 26, 54)

.hasMessageOnRange("Исправьте обращение к закрытому, неэкспортному методу РегистрацияИзмененийПередУдалением общего модуля ПервыйОбщийМодуль", 11, 22, 56)
.hasMessageOnRange("Исправьте обращение к закрытому, неэкспортному методу Тест общего модуля ПервыйОбщийМодуль", 12, 26, 30)
.hasMessageOnRange("Исправьте обращение к закрытому, неэкспортному методу Тест общего модуля ПервыйОбщийМодуль", 13, 22, 26)
.hasMessageOnRange("Исправьте обращение к закрытому, неэкспортному методу Тест общего модуля ПервыйОбщийМодуль", 14, 22, 26)
.hasMessageOnRange("Исправьте обращение к закрытому, неэкспортному методу Тест общего модуля ПервыйОбщийМодуль", 15, 26, 30)
.hasSize(10);
}

@Test
void testWithoutMetadata() {

List<Diagnostic> diagnostics = getDiagnostics();

assertThat(diagnostics).isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Процедура Тест1()
ПервыйОбщийМодуль.МетодНесуществующий(1, 2); // ошибка
А = ПервыйОбщийМодуль.ДругойМетодНесуществующий(); // ошибка
ПервыйОбщийМодуль.ЕщеМетодНесуществующий().Добавить(); // ошибка
ПервыйОбщийМодуль.ЕщеОдинМетодНесуществующий().Реквизит = 10; // ошибка
Б = ПервыйОбщийМодуль.ЕщеДругойМетодНесуществующий().Добавить(); // ошибка

НесуществующийОбщийМодульИлиПростоПеременная.МетодНесуществующий(1, 2); // не ошибка
КонецПроцедуры

Процедура Тест2_ОбращениеКПриватномуМетоду()
ПервыйОбщийМодуль.РегистрацияИзмененийПередУдалением(Источник, Отказ); // ошибка
А = ПервыйОбщийМодуль.Тест(); // ошибка
ПервыйОбщийМодуль.Тест().Добавить(); // ошибка
ПервыйОбщийМодуль.Тест().Реквизит = 10; // ошибка
Б = ПервыйОбщийМодуль.Тест().Добавить(); // ошибка
КонецПроцедуры

Процедура Тест3()
ПервыйОбщийМодуль.НеУстаревшаяПроцедура(); // не ошибка
А = ПервыйОбщийМодуль.НеУстаревшаяФункция(); // не ошибка
ПервыйОбщийМодуль.НеУстаревшаяФункция().Добавить(); // не ошибка
ПервыйОбщийМодуль.НеУстаревшаяФункция().Реквизит = 10; // не ошибка
Б = ПервыйОбщийМодуль.НеУстаревшаяФункция().Добавить(); // не ошибка
КонецПроцедуры

Процедура Тест4_ИмяПараметр(ПервыйОбщийМодуль)
ПервыйОбщийМодуль.МетодНесуществующий(1, 2); // не ошибка
А = ПервыйОбщийМодуль.ДругойМетодНесуществующий(); // не ошибка
ПервыйОбщийМодуль.ЕщеМетодНесуществующий().Добавить(); // не ошибка
ПервыйОбщийМодуль.ЕщеОдинМетодНесуществующий().Реквизит = 10; // не ошибка
Б = ПервыйОбщийМодуль.ЕщеДругойМетодНесуществующий().Добавить(); // не ошибка
КонецПроцедуры

Процедура Тест5_МодулиМенеджеров()
Справочники.Справочник1.НесуществующийМетод(); // пока не ошибка
КонецПроцедуры

0 comments on commit 85d0907

Please sign in to comment.