diff --git a/docs/diagnostics/DeprecatedHttpConnectionMethod.md b/docs/diagnostics/DeprecatedHttpConnectionMethod.md new file mode 100644 index 00000000000..12ec1c61882 --- /dev/null +++ b/docs/diagnostics/DeprecatedHttpConnectionMethod.md @@ -0,0 +1,38 @@ +# Устаревшие методы объекта HTTPСоединение (DeprecatedHttpConnectionMethod) + + +## Описание диагностики + +В платформе 8.3.21 методы объекта `HTTPСоединение` объявлены устаревшими в клиентском контексте. Вместо них следует использовать асинхронные аналоги с суффиксом `Асинх`. + +Асинхронные методы доступны только на клиенте. Поэтому диагностика срабатывает только в модулях с клиентским контекстом (формы, команды, обычное/управляемое приложение, клиентские общие модули) и только внутри методов, выполняющихся на клиенте (`&НаКлиенте`, `&НаКлиентеНаСервере` или без директивы в клиентском модуле). В методах с директивами `&НаСервере` и `&НаСервереБезКонтекста`, а также в серверных модулях диагностика не срабатывает — там асинхронных аналогов нет. + +## Устаревшие методы + +- `ВызватьHTTPМетод()` → `ВызватьHTTPМетодАсинх()` +- `Записать()` → `ЗаписатьАсинх()` +- `Изменить()` → `ИзменитьАсинх()` +- `ОтправитьДляОбработки()` → `ОтправитьДляОбработкиАсинх()` +- `Получить()` → `ПолучитьАсинх()` +- `ПолучитьЗаголовки()` → `ПолучитьЗаголовкиАсинх()` +- `Удалить()` → `УдалитьАсинх()` + +## Примеры + +### Неправильно + +```bsl +Соединение = Новый HTTPСоединение("example.com"); +Соединение.Получить(Запрос, Ответ); +``` + +### Правильно + +```bsl +Соединение = Новый HTTPСоединение("example.com"); +Соединение.ПолучитьАсинх(Запрос, Ответ); +``` + +## См. также + +- [Изменения платформы 8.3.21](https://dl04.1c.ru/content/Platform/8_3_21_1140/1cv8upd_8_3_21_1140.htm) diff --git a/docs/en/diagnostics/DeprecatedHttpConnectionMethod.md b/docs/en/diagnostics/DeprecatedHttpConnectionMethod.md new file mode 100644 index 00000000000..591259ce837 --- /dev/null +++ b/docs/en/diagnostics/DeprecatedHttpConnectionMethod.md @@ -0,0 +1,38 @@ +# Deprecated HTTPConnection methods (DeprecatedHttpConnectionMethod) + + +## Diagnostic description + +In platform 8.3.21, `HTTPConnection` object methods are deprecated in client context. Use their asynchronous equivalents with the `Async` suffix instead. + +Asynchronous methods are only available on the client. Therefore the diagnostic only fires in modules with a client context (forms, commands, ordinary/managed application, client common modules) and only inside methods that execute on the client (`&AtClient`, `&AtClientAtServer` or no directive in a client module). Methods with `&AtServer` and `&AtServerNoContext` directives, as well as server modules, are not reported — there are no asynchronous equivalents available there. + +## Deprecated methods + +- `CallHTTPMethod()` → `CallHTTPMethodAsync()` +- `Write()` → `WriteAsync()` +- `Change()` → `ChangeAsync()` +- `SendForProcessing()` → `SendForProcessingAsync()` +- `Get()` → `GetAsync()` +- `GetHeaders()` → `GetHeadersAsync()` +- `Delete()` → `DeleteAsync()` + +## Examples + +### Incorrect + +```bsl +Connection = New HTTPConnection("example.com"); +Connection.Get(Request, Response); +``` + +### Correct + +```bsl +Connection = New HTTPConnection("example.com"); +Connection.GetAsync(Request, Response); +``` + +## See also + +- [Platform 8.3.21 changelog](https://dl04.1c.ru/content/Platform/8_3_21_1140/1cv8upd_8_3_21_1140.htm) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic.java new file mode 100644 index 00000000000..a88569eb00f --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic.java @@ -0,0 +1,166 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2026 + * 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.DocumentContext; +import com.github._1c_syntax.bsl.languageserver.context.symbol.annotations.CompilerDirectiveKind; +import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticCompatibilityMode; +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.types.TypeService; +import com.github._1c_syntax.bsl.parser.BSLParser; +import com.github._1c_syntax.bsl.types.ModuleType; +import com.github._1c_syntax.bsl.mdo.CommonModule; +import com.github._1c_syntax.utils.CaseInsensitivePattern; +import lombok.RequiredArgsConstructor; +import org.antlr.v4.runtime.tree.ParseTree; + +import java.util.EnumSet; +import java.util.Set; +import java.util.regex.Pattern; + +@DiagnosticMetadata( + type = DiagnosticType.CODE_SMELL, + severity = DiagnosticSeverity.MAJOR, + scope = DiagnosticScope.BSL, + minutesToFix = 5, + compatibilityMode = DiagnosticCompatibilityMode.COMPATIBILITY_MODE_8_3_21, + tags = { + DiagnosticTag.DEPRECATED, + DiagnosticTag.PERFORMANCE + } +) +/** + * Диагностика устаревших методов объекта {@code HTTPСоединение} / + * {@code HTTPConnection}. + *

+ * В платформе 8.3.21 синхронные методы HTTP-соединения объявлены + * устаревшими в клиентском контексте. Вместо них следует использовать + * асинхронные аналоги с суффиксом {@code Асинх} / {@code Async}. + *

+ * Асинхронные методы доступны только на клиенте. Чтобы исключить ложные + * срабатывания, диагностика работает только в модулях с клиентским + * контекстом (формы, команды, обычное/управляемое приложение, клиентские + * общие модули) и только внутри методов, выполняющихся на клиенте + * (директивы {@code &НаКлиенте}, {@code &НаКлиентеНаСервере} и методы без + * директивы в клиентском модуле). + *

+ * Для исключения ложных срабатываний тип-владелец метода резолвится + * через {@link TypeService#memberAt}: диагностика срабатывает только + * если метод вызван на объекте типа {@code HTTPСоединение}. + * + * @see + * Изменения платформы 8.3.21 + */ +@RequiredArgsConstructor +public class DeprecatedHttpConnectionMethodDiagnostic extends AbstractVisitorDiagnostic { + + private static final Pattern MESSAGE_PATTERN = CaseInsensitivePattern.compile( + "(ВызватьHTTPМетод|CallHTTPMethod|" + + "Записать|Write|" + + "Изменить|Change|Modify|" + + "ОтправитьДляОбработки|SendForProcessing|" + + "Получить|Get|" + + "ПолучитьЗаголовки|GetHeaders|" + + "Удалить|Delete)" + ); + + private static final Pattern HTTP_CONNECTION_PATTERN = CaseInsensitivePattern.compile( + "HTTPСоединение|HTTPConnection" + ); + + private static final Set SERVER_COMPILER_DIRECTIVES = + EnumSet.of(CompilerDirectiveKind.AT_SERVER, CompilerDirectiveKind.AT_SERVER_NO_CONTEXT); + + private final TypeService typeService; + + @Override + public ParseTree visitFile(BSLParser.FileContext ctx) { + if (isServerModule(documentContext)) { + return ctx; + } + return super.visitFile(ctx); + } + + @Override + public ParseTree visitSub(BSLParser.SubContext ctx) { + var methodSymbol = documentContext.getSymbolTree().getMethodSymbol(ctx); + if (methodSymbol.isPresent()) { + var compilerDirective = methodSymbol.get().getCompilerDirectiveKind(); + if (compilerDirective.isPresent() + && SERVER_COMPILER_DIRECTIVES.contains(compilerDirective.get())) { + return ctx; + } + } + return super.visitSub(ctx); + } + + @Override + public ParseTree visitMethodCall(BSLParser.MethodCallContext ctx) { + var methodName = ctx.methodName(); + if (methodName == null) { + return ctx; + } + + if (!MESSAGE_PATTERN.matcher(methodName.getText()).matches()) { + return ctx; + } + + var identifier = methodName.IDENTIFIER(); + if (identifier == null) { + return ctx; + } + + var typedMember = typeService.memberAt(documentContext, identifier); + if (typedMember.isEmpty()) { + return ctx; + } + + var owner = typedMember.get().owner(); + if (owner != null && HTTP_CONNECTION_PATTERN.matcher(owner.qualifiedName()).matches()) { + diagnosticStorage.addDiagnostic(methodName, info.getMessage(methodName.getText())); + } + + return ctx; + } + + private static boolean isServerModule(DocumentContext documentContext) { + return switch (documentContext.getModuleType()) { + case ApplicationModule, CommandModule, FormModule, ManagedApplicationModule -> false; + case CommonModule -> isServerCommonModule(documentContext); + default -> true; // Все прочие модули — строго серверные + }; + } + + private static boolean isServerCommonModule(DocumentContext documentContext) { + var mdObject = documentContext.getMdObject(); + + return mdObject.map(CommonModule.class::cast) + .filter(commonModule -> !(commonModule.isClientManagedApplication() + || commonModule.isClientOrdinaryApplication())) + .isPresent(); + } + +} diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic_en.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic_en.properties new file mode 100644 index 00000000000..ee2bc4a0ff7 --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic_en.properties @@ -0,0 +1,2 @@ +diagnosticMessage=Method "%s" of HTTPConnection object is deprecated. Use the asynchronous equivalent with "Async" suffix +diagnosticName=Deprecated HTTPConnection methods diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic_ru.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic_ru.properties new file mode 100644 index 00000000000..540c1b4f386 --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnostic_ru.properties @@ -0,0 +1,2 @@ +diagnosticMessage=Метод "%s" объекта HTTPСоединение объявлен устаревшим. Используйте асинхронный аналог с суффиксом "Асинх" +diagnosticName=Устаревшие методы объекта HTTPСоединение diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnosticTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnosticTest.java new file mode 100644 index 00000000000..e8daeaa93ea --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DeprecatedHttpConnectionMethodDiagnosticTest.java @@ -0,0 +1,99 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2026 + * 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.DocumentContext; +import com.github._1c_syntax.bsl.languageserver.types.TypeService; +import com.github._1c_syntax.bsl.languageserver.types.model.MemberDescriptor; +import com.github._1c_syntax.bsl.languageserver.types.model.TypeKind; +import com.github._1c_syntax.bsl.languageserver.types.model.TypeRef; +import com.github._1c_syntax.bsl.languageserver.util.TestUtils; +import com.github._1c_syntax.bsl.languageserver.utils.Ranges; +import com.github._1c_syntax.bsl.types.ModuleType; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.eclipse.lsp4j.Diagnostic; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Optional; + +import static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +class DeprecatedHttpConnectionMethodDiagnosticTest + extends AbstractDiagnosticTest { + + DeprecatedHttpConnectionMethodDiagnosticTest() { + super(DeprecatedHttpConnectionMethodDiagnostic.class); + } + + @Test + void testOnArrayDoesNotFire() { + initServerContext(TestUtils.PATH_TO_METADATA); + var documentContext = spy(getDocumentContext()); + when(documentContext.getModuleType()).thenReturn(ModuleType.FormModule); + List diagnostics = getDiagnostics(documentContext); + + // Массив.Получить/Удалить совпадают по имени, но TypeService + // резолвит владельца как Массив (не HTTPСоединение) → 0 срабатываний. + assertThat(diagnostics).isEmpty(); + } + + @Test + void testServerModuleDoesNotFire() { + var documentContext = spy(getDocumentContext()); + when(documentContext.getModuleType()).thenReturn(ModuleType.ObjectModule); + List diagnostics = getDiagnostics(documentContext); + + // Серверный модуль — visitFile возвращает ctx без обхода → 0. + assertThat(diagnostics).isEmpty(); + } + + @Test + void testServerDirectiveDoesNotFireButClientFires() { + var typeService = mock(TypeService.class); + var owner = new TypeRef(TypeKind.PLATFORM, "HTTPСоединение"); + var typedMember = new TypeService.TypedMember( + owner, + MemberDescriptor.method("Получить"), + Ranges.create(0, 0, 8), + 0 + ); + when(typeService.memberAt(any(DocumentContext.class), any(TerminalNode.class))) + .thenReturn(Optional.of(typedMember)); + + var diagnostic = new DeprecatedHttpConnectionMethodDiagnostic(typeService); + diagnostic.setInfo(diagnosticInstance.getInfo()); + + var documentContext = spy(getDocumentContext()); + when(documentContext.getModuleType()).thenReturn(ModuleType.FormModule); + + List diagnostics = diagnostic.getDiagnostics(documentContext); + + // Фикстура: &НаКлиенте метод (срабатывает), &НаСервере и + // &НаСервереБезКонтекста (не срабатывают) → ровно 1 диагностика. + assertThat(diagnostics).hasSize(1); + } +} diff --git a/src/test/resources/diagnostics/DeprecatedHttpConnectionMethodDiagnostic.bsl b/src/test/resources/diagnostics/DeprecatedHttpConnectionMethodDiagnostic.bsl new file mode 100644 index 00000000000..0ddd3361d19 --- /dev/null +++ b/src/test/resources/diagnostics/DeprecatedHttpConnectionMethodDiagnostic.bsl @@ -0,0 +1,18 @@ +&НаКлиенте +Процедура КлиентскийМетод() + Соединение = Новый HTTPСоединение("example.com"); + Соединение.Получить(Запрос, Ответ); +КонецПроцедуры + +&НаСервере +Процедура СерверныйМетод() + Соединение = Новый HTTPСоединение("example.com"); + Соединение.Получить(Запрос, Ответ); +КонецПроцедуры + +&НаСервереБезКонтекста +Функция СервернаяФункция() + Соединение = Новый HTTPСоединение("example.com"); + Соединение.ПолучитьЗаголовки(Запрос); + Возврат Истина; +КонецФункции