Skip to content

Commit 9cc70e5

Browse files
authored
feat(diagnostic): #2861 Реализовано правило "Обработчик регламентного задания"
1 parent 2e933d5 commit 9cc70e5

15 files changed

+568
-12
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Обработчик регламентного задания (ScheduledJobHandler)
2+
3+
<!-- Блоки выше заполняются автоматически, не трогать -->
4+
## Описание диагностики
5+
<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->
6+
К обработчикам регламентных заданий предъявляются определенные требования.
7+
В качестве метода регламентного задания может выступать любая экспортная процедура или функция неглобального общего серверного модуля. Если методом регламентного задания является функция, то ее возвращаемое значение игнорируется.
8+
9+
Если регламентное задание является предопределенным, то у его обработчика не должно быть параметров.
10+
Если нет, тогда параметрами подобного регламентного задания могут быть любые значения, которые разрешено передавать на сервер. Параметры регламентного задания должны в точности соответствовать параметрам той процедуры или функции, которую оно вызывает.
11+
12+
Правило проверяет следующие признаки валидности метода-обработчика регламентного задания:
13+
- существует и общий модуль и метод общего модуля, указанные как обработчик
14+
- общий модуль является серверным
15+
- метод является экспортным
16+
- у метода нет параметров, если регламентное задание является предопределенным.
17+
- задано тело метода
18+
- нет двух и более регламентных заданий, которые ссылаются на один и тот же метод-обработчик
19+
20+
## Примеры
21+
<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->
22+
23+
## Источники
24+
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
25+
<!-- Примеры источников
26+
27+
* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
28+
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
29+
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->
30+
- [Набор статей "Регламентные задания" - стандарт 1С ](https://its.1c.ru/db/v8std#browse:13:-1:1:6)
31+
- [Статья "Регламентные задания" из руководства разработчика 1С 8.3](https://its.1c.ru/db/v8322doc#bookmark:dev:TI000000794)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Scheduled job handler (ScheduledJobHandler)
2+
3+
<!-- Блоки выше заполняются автоматически, не трогать -->
4+
## Description
5+
<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->
6+
7+
## Examples
8+
<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->
9+
10+
## Sources
11+
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
12+
<!-- Примеры источников
13+
14+
* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
15+
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
16+
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->

src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/AbstractMetadataDiagnostic.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,20 @@ protected void addDiagnostic(String message) {
108108
protected abstract void checkMetadata(AbstractMDObjectBase mdo);
109109

110110
private void checkMetadataWithModules() {
111-
documentContext.getMdObject().ifPresent((AbstractMDObjectBase mdo) -> {
112-
if (mdo instanceof AbstractMDObjectBSL) {
113-
var modules = ((AbstractMDObjectBSL) mdo).getModules().stream()
114-
.filter(mdoModule -> OBJECT_MODULES.contains(mdoModule.getModuleType()))
115-
.collect(Collectors.toList());
116-
117-
// чтобы не анализировать несколько раз, выберем только один модуль, например модуль менеджера
118-
if (modules.size() == 1 || documentContext.getModuleType() == ModuleType.ManagerModule) {
119-
checkMetadata(mdo);
120-
}
121-
}
122-
});
111+
documentContext.getMdObject()
112+
.filter(mdo -> filterMdoTypes.contains(mdo.getMdoType()))
113+
.filter(AbstractMDObjectBSL.class::isInstance)
114+
.filter(this::haveMatchingModule)
115+
.ifPresent(this::checkMetadata);
116+
}
117+
118+
private boolean haveMatchingModule(AbstractMDObjectBase mdo) {
119+
var modules = ((AbstractMDObjectBSL) mdo).getModules().stream()
120+
.filter(mdoModule -> OBJECT_MODULES.contains(mdoModule.getModuleType()))
121+
.collect(Collectors.toList());
122+
123+
// чтобы не анализировать несколько раз, выберем только один модуль, например модуль менеджера
124+
return modules.size() == 1 || documentContext.getModuleType() == ModuleType.ManagerModule;
123125
}
124126

125127
/**
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* This file is a part of BSL Language Server.
3+
*
4+
* Copyright (c) 2018-2022
5+
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> and contributors
6+
*
7+
* SPDX-License-Identifier: LGPL-3.0-or-later
8+
*
9+
* BSL Language Server is free software; you can redistribute it and/or
10+
* modify it under the terms of the GNU Lesser General Public
11+
* License as published by the Free Software Foundation; either
12+
* version 3.0 of the License, or (at your option) any later version.
13+
*
14+
* BSL Language Server is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
* Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public
20+
* License along with BSL Language Server.
21+
*/
22+
package com.github._1c_syntax.bsl.languageserver.diagnostics;
23+
24+
import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;
25+
import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol;
26+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
27+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticScope;
28+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity;
29+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag;
30+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType;
31+
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex;
32+
import com.github._1c_syntax.bsl.types.MDOType;
33+
import com.github._1c_syntax.bsl.types.ModuleType;
34+
import com.github._1c_syntax.mdclasses.mdo.AbstractMDObjectBase;
35+
import com.github._1c_syntax.mdclasses.mdo.MDCommonModule;
36+
import com.github._1c_syntax.mdclasses.mdo.MDScheduledJob;
37+
38+
import java.util.ArrayList;
39+
import java.util.Comparator;
40+
import java.util.HashMap;
41+
import java.util.List;
42+
import java.util.Map;
43+
44+
@DiagnosticMetadata(
45+
type = DiagnosticType.ERROR,
46+
severity = DiagnosticSeverity.CRITICAL,
47+
minutesToFix = 5,
48+
tags = {
49+
DiagnosticTag.ERROR
50+
},
51+
scope = DiagnosticScope.BSL
52+
)
53+
54+
public class ScheduledJobHandlerDiagnostic extends AbstractMetadataDiagnostic {
55+
56+
private static final String DIAGNOSTIC_MESSAGE = "diagnosticMessage";
57+
private static final String MISSING_MODULE_MESSAGE = "missingModule";
58+
private static final String NON_SERVER_MODULE_MESSAGE = "nonServerModule";
59+
private static final String NON_EXPORT_METHOD_MESSAGE = "nonExportMethod";
60+
private static final String METHOD_WITH_PARAMETERS_MESSAGE = "methodWithParameters";
61+
private static final String EMPTY_METHOD_MESSAGE = "emptyMethod";
62+
private static final String DOUBLE_MESSAGE = "doubleMessage";
63+
64+
private final ReferenceIndex referenceIndex;
65+
private final Map<String, List<MDScheduledJob>> scheduledJobHandlers = new HashMap<>();
66+
67+
private static String getFullName(MDCommonModule mdCommonModule, String methodName) {
68+
return getFullName(mdCommonModule.getName(), methodName);
69+
}
70+
71+
private static String getFullName(String commonModuleName, String methodName) {
72+
return commonModuleName.concat(".").concat(methodName);
73+
}
74+
75+
public ScheduledJobHandlerDiagnostic(ReferenceIndex referenceIndex) {
76+
super(List.of(MDOType.SCHEDULED_JOB));
77+
this.referenceIndex = referenceIndex;
78+
}
79+
80+
@Override
81+
protected void check() {
82+
super.check();
83+
checkHandlerDoubles();
84+
}
85+
86+
private void checkHandlerDoubles() {
87+
scheduledJobHandlers.values().stream()
88+
.filter(mdScheduledJobs -> mdScheduledJobs.size() > 1)
89+
.map((List<MDScheduledJob> mdScheduledJobs) -> {
90+
mdScheduledJobs.sort(Comparator.comparing(AbstractMDObjectBase::getName));
91+
return mdScheduledJobs;
92+
})
93+
.forEach(this::fireIssueForDoubles);
94+
scheduledJobHandlers.clear();
95+
}
96+
97+
private void fireIssueForDoubles(List<MDScheduledJob> mdScheduledJobs) {
98+
final var scheduleJobNames = mdScheduledJobs.stream()
99+
.map(AbstractMDObjectBase::getName)
100+
.reduce((s, s2) -> s.concat(", ").concat(s2))
101+
.orElseThrow();
102+
final var mdScheduledJob = mdScheduledJobs.get(0).getHandler();
103+
final var methodPath = getFullName(mdScheduledJob.getModuleName(), mdScheduledJob.getMethodName());
104+
105+
addDiagnostic(info.getResourceString(DOUBLE_MESSAGE, methodPath, scheduleJobNames));
106+
}
107+
108+
@Override
109+
protected void checkMetadata(AbstractMDObjectBase mdo) {
110+
final var scheduleJob = (MDScheduledJob) mdo;
111+
final var handler = scheduleJob.getHandler();
112+
if (handler.isEmpty()) {
113+
addDiagnostic(scheduleJob);
114+
return;
115+
}
116+
117+
final var moduleName = handler.getModuleName();
118+
119+
final var commonModuleOptional =
120+
documentContext.getServerContext().getConfiguration().getCommonModule(moduleName);
121+
if (commonModuleOptional.isEmpty()) {
122+
addDiagnostic(MISSING_MODULE_MESSAGE, scheduleJob, moduleName);
123+
return;
124+
}
125+
final var mdCommonModule = commonModuleOptional.orElseThrow();
126+
if (!mdCommonModule.isServer()) {
127+
addDiagnostic(NON_SERVER_MODULE_MESSAGE, scheduleJob, moduleName);
128+
return;
129+
}
130+
checkMethod(scheduleJob, mdCommonModule, handler.getMethodName());
131+
}
132+
133+
private void checkMethod(MDScheduledJob scheduleJob, MDCommonModule mdCommonModule, String methodName) {
134+
final var fullName = getFullName(mdCommonModule, methodName);
135+
scheduledJobHandlers.computeIfAbsent(fullName, k -> new ArrayList<>()).add(scheduleJob);
136+
137+
documentContext.getServerContext().getDocument(
138+
mdCommonModule.getMdoReference().getMdoRef(), ModuleType.CommonModule)
139+
.ifPresent((DocumentContext commonModuleContext) -> {
140+
var method = commonModuleContext.getSymbolTree().getMethods().stream()
141+
.filter(methodSymbol -> methodSymbol.getName().equalsIgnoreCase(methodName))
142+
.findFirst();
143+
if (method.isEmpty()) {
144+
addDiagnostic(DIAGNOSTIC_MESSAGE, scheduleJob, fullName);
145+
return;
146+
}
147+
method.ifPresent((MethodSymbol methodSymbol) -> checkMethod(scheduleJob, fullName, methodSymbol));
148+
});
149+
}
150+
151+
private void checkMethod(MDScheduledJob scheduleJob, String fullName, MethodSymbol methodSymbol) {
152+
if (!methodSymbol.isExport()) {
153+
addDiagnostic(NON_EXPORT_METHOD_MESSAGE, scheduleJob, fullName);
154+
}
155+
if (scheduleJob.isPredefined() && !methodSymbol.getParameters().isEmpty()) {
156+
addDiagnostic(METHOD_WITH_PARAMETERS_MESSAGE, scheduleJob, fullName);
157+
}
158+
if (isEmptyMethodBody(methodSymbol)) {
159+
addDiagnostic(EMPTY_METHOD_MESSAGE, scheduleJob, fullName);
160+
}
161+
}
162+
163+
private boolean isEmptyMethodBody(MethodSymbol methodSymbol) {
164+
// В методе регламентного задания точно будут или переменные или вызов внешнего метода.
165+
// Если их нет, значит, метод пустой
166+
if (!methodSymbol.getChildren().isEmpty()) {
167+
return false;
168+
}
169+
return referenceIndex.getReferencesFrom(methodSymbol).isEmpty();
170+
}
171+
172+
private void addDiagnostic(String messageString, MDScheduledJob scheduleJob, String text) {
173+
addDiagnostic(info.getResourceString(messageString, text, scheduleJob.getName()));
174+
}
175+
176+
private void addDiagnostic(MDScheduledJob scheduleJob) {
177+
addDiagnostic(info.getMessage("", scheduleJob.getName()));
178+
}
179+
}

src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,16 @@
15401540
"title": "Same metadata object and child name",
15411541
"$id": "#/definitions/SameMetadataObjectAndChildNames"
15421542
},
1543+
"ScheduledJobHandler": {
1544+
"description": "Scheduled job handler",
1545+
"default": true,
1546+
"type": [
1547+
"boolean",
1548+
"object"
1549+
],
1550+
"title": "Scheduled job handler",
1551+
"$id": "#/definitions/ScheduledJobHandler"
1552+
},
15431553
"SelectTopWithoutOrderBy": {
15441554
"description": "Using 'SELECT TOP' without 'ORDER BY'",
15451555
"default": true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
diagnosticMessage=Укажите существующий обработчик вместо несуществующего "%s" у регламентного задания "%s"
2+
diagnosticName=Scheduled job handler
3+
4+
missingModule=Создайте общий модуль "%s" или исправьте некорректный обработчик регламентного задания "%s"
5+
nonServerModule=Установите флаг "Сервер" общему модулю "%s" или исправьте некорректный обработчик регламентного задания "%s"
6+
nonExportMethod=Добавьте "Экспорт" методу "%s" или исправьте некорректный обработчик регламентного задания "%s"
7+
methodWithParameters=Исправьте некорректный обработчик "%s" предопределенного регламентного задания "%s" - у метода не должно быть параметров
8+
emptyMethod=Добавьте код в тело обработчика "%s" регламентного задания "%s"
9+
doubleMessage=Исправьте дубли использования одного обработчика "%s" в разных регламентных заданиях. Задания: "%s"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
diagnosticMessage=Укажите существующий обработчик вместо несуществующего "%s" у регламентного задания "%s"
2+
diagnosticName=Обработчик регламентного задания
3+
4+
missingModule=Создайте общий модуль "%s" или исправьте некорректный обработчик регламентного задания "%s"
5+
nonServerModule=Установите флаг "Сервер" общему модулю "%s" или исправьте некорректный обработчик регламентного задания "%s"
6+
nonExportMethod=Добавьте "Экспорт" методу "%s" или исправьте некорректный обработчик регламентного задания "%s"
7+
methodWithParameters=Исправьте некорректный обработчик "%s" предопределенного регламентного задания "%s" - у метода не должно быть параметров
8+
emptyMethod=Добавьте код в тело обработчика "%s" регламентного задания "%s"
9+
doubleMessage=Исправьте дубли использования одного обработчика "%s" в разных регламентных заданиях. Задания: "%s"

0 commit comments

Comments
 (0)